Levered Docs
SDK

React

React components and hooks for serving optimized variants with Levered.

The React integration provides a provider component and a hook that handle variant fetching, caching, loading states, and exposure tracking. Import everything from the react submodule:

import { LeveredProvider, useVariant } from '@levered_dev/sdk/react';

LeveredProvider

Wrap your application (or the subtree that needs variants) with LeveredProvider. It creates a shared LeveredClient instance and makes it available to all useVariant calls below it.

import { LeveredProvider } from '@levered_dev/sdk/react';

function App() {
  return (
    <LeveredProvider
      apiUrl="https://api.levered.dev"
      anonymousId={getOrCreateSessionId()}
      onExposure={(event) => {
        // Log to your warehouse (BigQuery, Snowflake, Segment, etc.)
        analytics.track('levered_exposure', event);
      }}
    >
      <YourApp />
    </LeveredProvider>
  );
}

Props

PropTypeRequiredDescription
apiUrlstringYesBase URL of the Levered API (e.g. https://api.levered.dev).
anonymousIdstringYesStable anonymous identifier for the current user or session. Must match the anonymous_id column in your warehouse exposure table.
onExposure(event: ExposureEvent) => voidNoCalled every time the SDK exposes a user to a variant. Use this to log exposure events to your warehouse.

The anonymousId should be a persistent identifier that you generate and store (e.g. in a cookie or local storage). It ties the variant assignment to the user so Levered can learn which variants perform best.


useVariant

Fetch the best variant for a given optimization. The hook returns the variant values immediately (using the fallback while loading) so your component always has something to render.

import { useVariant } from '@levered_dev/sdk/react';

function HeroSection() {
  const { variant, isLoading } = useVariant({
    optimizationId: 'abc-123',
    fallback: { headline: 'Welcome', cta_text: 'Get Started' },
    context: { device: 'mobile' },
  });

  return (
    <section>
      <h1>{variant.headline}</h1>
      <button>{variant.cta_text}</button>
    </section>
  );
}

Options

OptionTypeRequiredDescription
optimizationIdstringYesUUID of the optimization to serve variants from.
fallbackTYesDefault variant values shown while loading and on error. Also defines the TypeScript shape of the variant object.
contextRecord<string, unknown>NoContext attributes for contextual bandits (CMAB). For example: { device: 'mobile', country: 'US' }.

Return value

FieldTypeDescription
variantTThe resolved variant values. Equals fallback while loading or if the request fails.
isLoadingbooleantrue while the initial fetch is in flight.

Type safety

The fallback prop defines the shape of the variant object. TypeScript will infer the type from your fallback, so variant.headline and variant.cta_text are strongly typed.

// TypeScript knows variant has { headline: string; cta_text: string }
const { variant } = useVariant({
  optimizationId: 'abc-123',
  fallback: { headline: 'Welcome', cta_text: 'Get Started' },
});

Re-fetching

The hook automatically re-fetches when optimizationId, anonymousId (from the provider), or context changes. You do not need to manage refetching yourself.


ExposureEvent

When the SDK assigns a variant to a user, it fires the onExposure callback with an event that has this shape:

interface ExposureEvent {
  /** The user's anonymous identifier. */
  anonymousId: string;
  /** UUID of the optimization that served this variant. */
  optimizationId: string;
  /** The design-factor values of the assigned variant. */
  variant: Record<string, string | number | boolean>;
  /** Context attributes sent with the request. */
  context: Record<string, unknown>;
  /** ISO-8601 timestamp of the exposure. */
  timestamp: string;
  /** Which bandit model produced this routing: 'mab', 'cmab', or 'holdout'. */
  modelType: 'mab' | 'cmab' | 'holdout';
  /** Whether the model had enough data to route (true) or fell back to random sampling (false). */
  ready: boolean;
}

You must forward this event to your data warehouse. Levered reads exposure data from your warehouse during model training -- if exposures are not logged, the model cannot learn.


LeveredAdminMenu

A debug overlay for manually overriding variant assignments during development. It renders a docked right-edge drawer labelled "Variant Preview" that lets you pick specific values for each design feature.

import { LeveredAdminMenu } from '@levered_dev/sdk/react';

function App() {
  const { variant, isLoading } = useVariant({
    optimizationId: 'abc-123',
    fallback: { headline: 'Welcome', cta_text: 'Get Started' },
  });

  const [override, setOverride] = useState<Record<string, string | number | boolean> | null>(null);
  const activeVariant = override ?? variant;

  return (
    <>
      <HeroSection variant={activeVariant} />

      <LeveredAdminMenu
        enabled={process.env.NODE_ENV === 'development'}
        variants={[
          {
            name: 'headline',
            label: 'Headline',
            options: [
              { value: 'Welcome', label: 'Welcome' },
              { value: 'Hello there', label: 'Hello there' },
              { value: 'Try it free', label: 'Try it free' },
            ],
          },
          {
            name: 'cta_text',
            label: 'CTA Text',
            options: [
              { value: 'Get Started', label: 'Get Started' },
              { value: 'Sign Up Now', label: 'Sign Up Now' },
            ],
          },
        ]}
        currentValues={activeVariant}
        onOverride={(values) => setOverride(values)}
        onReset={() => setOverride(null)}
      />
    </>
  );
}

Props

PropTypeDefaultDescription
enabledbooleanfalseWhether to render the preview drawer.
variantsAdminVariantConfig[]--Array of design features with their possible values.
widthnumber272Drawer width in px.
currentValuesRecord<string, string | number | boolean>--The currently active variant values.
onOverride(values: Record<string, string | number | boolean>) => void--Called when the user selects a variant override.
onReset() => void--Called when the user clicks "Reset to Live". If not provided, the reset button is hidden.

AdminVariantConfig

Each entry in the variants array describes one design feature:

interface AdminVariantConfig {
  /** Property name (e.g. "headline"). */
  name: string;
  /** Display label in the UI (defaults to name). */
  label?: string;
  /** Possible values to switch between. */
  options: Array<{
    value: string | number | boolean;
    label?: string;
  }>;
}

Only enable the admin menu in development or staging environments. It is a debugging tool and should not be visible to end users.


Full example

Putting it all together -- provider, hook, exposure logging, and admin menu:

import { useState } from 'react';
import { LeveredProvider, useVariant, LeveredAdminMenu } from '@levered_dev/sdk/react';

function App() {
  return (
    <LeveredProvider
      apiUrl="https://api.levered.dev"
      anonymousId={getOrCreateSessionId()}
      onExposure={(event) => {
        analytics.track('levered_exposure', event);
      }}
    >
      <HeroPage />
    </LeveredProvider>
  );
}

function HeroPage() {
  const { variant, isLoading } = useVariant({
    optimizationId: 'abc-123',
    fallback: { headline: 'Welcome', cta_text: 'Get Started' },
  });

  const [override, setOverride] = useState<Record<string, string | number | boolean> | null>(null);
  const active = override ?? variant;

  return (
    <main>
      <h1>{active.headline}</h1>
      <button>{active.cta_text}</button>

      <LeveredAdminMenu
        enabled={process.env.NODE_ENV === 'development'}
        variants={[
          {
            name: 'headline',
            options: [
              { value: 'Welcome' },
              { value: 'Hello there' },
              { value: 'Try it free' },
            ],
          },
          {
            name: 'cta_text',
            options: [
              { value: 'Get Started' },
              { value: 'Sign Up Now' },
            ],
          },
        ]}
        currentValues={active}
        onOverride={setOverride}
        onReset={() => setOverride(null)}
      />
    </main>
  );
}