Developers

Theming & CSS variables

Theming is the skin tier of the Gateway Kit — you own the entire visual layer (color, radius, spacing, typography, motion, per-primitive surfaces) while the Kit keeps owning structure, FSM, and behavior. Layer it on top of any other tier: skin a drop-in dialog, skin a compound composition, skin a headless flow's primitives. Same provider, same FSM, every surface re-paints.

Everything visual about the Kit is driven by CSS custom properties. The appearance prop is a convenience layer on top — it sets the same variables you can override directly from your stylesheet. No JS theme API, no defineSkin() helper, no recompile step. Just CSS.

Two ways in

PathWhenWhere it lands
<StridgeProvider appearance={…}>Most teams. Theme, accent, radius preset, direction, attribution.Inlines a style attr on the scope root + sets data-stridge-* attributes.
Direct CSS overridesBrand-specific surfaces, per-primitive tweaks, locale-conditional fonts.Override --stridge-kit-* variables on [data-stridge-scope] (or theme-scoped selectors like [data-stridge-theme="light"]).

Both compose freely. The appearance prop covers the common case; direct CSS covers everything else.

The appearance prop

<StridgeProvider
  appearance={{
    theme: "dark",                       // "light" | "dark"   default "dark"
    accent: "oklch(64.51% 0.19 274.49)", // any CSS color
    radius: "rounded",                   // "sharp" | "subtle" | "rounded" | "pill"
    direction: "ltr",                    // "ltr" | "rtl"     auto-derived from locale
    attribution: "visible",              // "hidden" removes "Powered by Stridge"
    presentation: "auto",                // "auto" | "dialog" | "drawer", or { mode, breakpoint }
  }}
>

</StridgeProvider>

Under the hood:

  • themedata-stridge-theme="<value>" — swaps the entire color palette.
  • accentstyle="--stridge-kit-primary: <value>" — every CTA / focus ring / progress affordance rebinds in one place.
  • radiusdata-stridge-radius="<value>" — retunes the base radius token; per-primitive radii scale with it.
  • directiondir="…" on the scope plus data-stridge-flip-on-rtl markers on chevrons / arrows.
  • attribution → toggles the "Powered by Stridge" row.
  • presentation → the surface gateway dialogs render with. "auto" (default) picks a centred dialog above the breakpoint and a bottom-sheet drawer below it; "dialog" / "drawer" force one, and { mode, breakpoint } tunes the px threshold (default 600). Measured against the dialog's containing block. See Drop-in dialogs → Responsive presentation.

Direct CSS overrides

Every override lives under the scope root the provider mounts. Targeting [data-stridge-scope] (or a more specific selector like [data-stridge-theme="dark"]) keeps your changes scoped to the Kit and out of your wider app's CSS.

/* Light-theme tweak. */
[data-stridge-theme="light"] {
    --stridge-kit-primary: oklch(60% 0.15 250);
    --stridge-kit-secondary: oklch(50% 0.08 260);
}

/* Per-primitive surface tweak — see "Per-primitive namespaces" below. */
[data-stridge-scope] {
    --stridge-kit-card-surface: oklch(95% 0.01 250);
}

/* Locale-conditional font family. */
[data-stridge-locale^="fa"] {
    --stridge-kit-font-sans: Vazirmatn, sans-serif;
}

All token values use OKLCH internally; you can pass any CSS color the browser accepts.

Token reference

Color tokens

The Kit's color system is theme-paired — dark is the default; light applies under [data-stridge-theme="light"]. Every role-based token has a *-foreground companion. The values below are the dark-theme defaults shipped in @stridge/kit/styles.css.

:root,
[data-stridge-theme="dark"] {
    /* Core surfaces */
    --stridge-kit-background: oklch(16.57% 0.01 268.69);
    --stridge-kit-foreground: oklch(95.57% 0 286.35);
    --stridge-kit-card: oklch(19.58% 0 271.09);
    --stridge-kit-card-foreground: oklch(95.57% 0 286.35);
    --stridge-kit-popover: oklch(21.6% 0.01 270.3);
    --stridge-kit-popover-foreground: oklch(95.57% 0 286.35);

    /* Action + neutral states */
    --stridge-kit-primary: oklch(64.51% 0.19 274.49);
    --stridge-kit-primary-foreground: oklch(100.11% 0.01 289.34);
    --stridge-kit-secondary: oklch(23.29% 0 270.82);
    --stridge-kit-secondary-foreground: oklch(95.57% 0 286.35);
    --stridge-kit-muted: oklch(23.29% 0 270.82);
    --stridge-kit-muted-foreground: oklch(71.37% 0.02 261.32);
    --stridge-kit-muted-foreground-faint: oklch(52.95% 0.01 286.13);
    --stridge-kit-accent: oklch(30.65% 0.06 279.11);
    --stridge-kit-accent-foreground: oklch(95.57% 0 286.35);
    --stridge-kit-surface-hover: oklch(27.01% 0 271.23);

    /* Destructive — strong + soft tints used by banners and inline alerts */
    --stridge-kit-destructive: oklch(65.56% 0.2 23.26);
    --stridge-kit-destructive-foreground: oklch(100.84% 0.1 25.27);
    --stridge-kit-destructive-soft: oklch(22.22% 0.04 17.89);
    --stridge-kit-destructive-soft-foreground: oklch(72.04% 0.17 19.6);
    --stridge-kit-destructive-border: oklch(58.69% 0.2 22.75);
    --stridge-kit-destructive-ring: oklch(25.35% 0.06 19.5);

    /* Status colors */
    --stridge-kit-success: oklch(63.7% 0.17 146.75);
    --stridge-kit-success-foreground: oklch(99% 0.09 145.23);
    --stridge-kit-success-ring: oklch(46.99% 0.1 153.81);
    --stridge-kit-warning: oklch(71.95% 0.19 41.4);
    --stridge-kit-warning-foreground: oklch(100.55% 0.1 44.42);
    --stridge-kit-info: oklch(71.02% 0.14 206.64);
    --stridge-kit-info-foreground: oklch(99.51% 0.07 206.76);

    /* Borders and inputs */
    --stridge-kit-border: oklch(27.02% 0 270.93);
    --stridge-kit-input: oklch(27.02% 0 270.93);
    --stridge-kit-ring: var(--stridge-kit-primary); /* focus-ring stroke */
    --stridge-kit-overlay: oklch(0% 0 0 / 0.4); /* alpha-on-black for modals */
    --stridge-kit-icon-stroke: oklch(73.41% 0.01 286.12);
}

Typography tokens

:root {
    /* Stacks. Override these directly to swap fonts globally. */
    --stridge-kit-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
    --stridge-kit-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

    /* Type scale */
    --stridge-kit-text-2xs: 0.625rem;  /* 10px */
    --stridge-kit-text-xs: 0.75rem;    /* 12px */
    --stridge-kit-text-sm: 0.875rem;   /* 14px */
    --stridge-kit-text-base: 1rem;     /* 16px */
    --stridge-kit-text-lg: 1.125rem;   /* 18px */
    --stridge-kit-text-xl: 1.25rem;    /* 20px */
    --stridge-kit-text-2xl: 1.5rem;    /* 24px */
    --stridge-kit-text-3xl: 1.875rem;  /* 30px */
    --stridge-kit-text-4xl: 2.25rem;   /* 36px */

    /* Off-step gateway-canvas sizes — timer digits, mono addresses, callouts. */
    --stridge-kit-text-caption: 0.6875rem; /* 11px */
    --stridge-kit-text-meta: 0.8125rem;    /* 13px */
    --stridge-kit-text-callout: 0.9375rem; /* 15px */

    /* Line height */
    --stridge-kit-leading-none: 1;
    --stridge-kit-leading-tight: 1.25;
    --stridge-kit-leading-normal: 1.5;
    --stridge-kit-leading-relaxed: 1.625;

    /* Weight */
    --stridge-kit-font-weight-normal: 400;
    --stridge-kit-font-weight-medium: 500;
    --stridge-kit-font-weight-semibold: 600;
    --stridge-kit-font-weight-bold: 700;

    /* Letter-spacing */
    --stridge-kit-tracking-tighter: -0.025em;
    --stridge-kit-tracking-tight: -0.01em;
    --stridge-kit-tracking-normal: 0;
    --stridge-kit-tracking-wide: 0.02em;
    --stridge-kit-tracking-widest: 0.1em;
}

Spacing tokens

An 11-step scale based on 0.25rem (4px) increments.

:root {
    --stridge-kit-space-0: 0;
    --stridge-kit-space-1: 0.25rem;  /* 4px */
    --stridge-kit-space-2: 0.5rem;   /* 8px */
    --stridge-kit-space-3: 0.75rem;  /* 12px */
    --stridge-kit-space-4: 1rem;     /* 16px */
    --stridge-kit-space-5: 1.25rem;  /* 20px */
    --stridge-kit-space-6: 1.5rem;   /* 24px */
    --stridge-kit-space-8: 2rem;     /* 32px */
    --stridge-kit-space-10: 2.5rem;  /* 40px */
    --stridge-kit-space-12: 3rem;    /* 48px */
    --stridge-kit-space-16: 4rem;    /* 64px */
}

Radius tokens

The Kit ships four radius presets. Each preset retunes the base --stridge-kit-radius and every per-primitive radius token in lockstep, so a single attribute switch reshapes every corner in the flow.

PresetValueFeel
sharp0pxSquared edges; common in trading terminals.
subtle6pxLight softening.
rounded12px (default)Default — balances softness with information density.
pill9999pxFully rounded; consumer-grade brand.
:root {
    --stridge-kit-radius: 0.75rem; /* base — retuned per preset */
    --stridge-kit-radius-xs: calc(var(--stridge-kit-radius) - 6px);
    --stridge-kit-radius-sm: calc(var(--stridge-kit-radius) - 4px);
    --stridge-kit-radius-md: calc(var(--stridge-kit-radius) - 2px);
    --stridge-kit-radius-lg: var(--stridge-kit-radius);
    --stridge-kit-radius-xl: calc(var(--stridge-kit-radius) + 4px);
    --stridge-kit-radius-2xl: calc(var(--stridge-kit-radius) + 8px);
    --stridge-kit-radius-full: 9999px;
}

You can override the base directly to land between presets:

[data-stridge-scope] {
    --stridge-kit-radius: 10px;
}

Motion tokens

Three durations × five easings cover every Kit animation.

:root {
    --stridge-kit-duration-fast: 150ms;
    --stridge-kit-duration-normal: 200ms;
    --stridge-kit-duration-slow: 300ms;

    --stridge-kit-ease-in: cubic-bezier(0.4, 0, 1, 1);
    --stridge-kit-ease-out: cubic-bezier(0, 0, 0.2, 1);
    --stridge-kit-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
    --stridge-kit-ease-enter: cubic-bezier(0.16, 1, 0.3, 1); /* spring-out */
    --stridge-kit-ease-exit: cubic-bezier(0.4, 0, 1, 1);     /* sharp linear-out */
}

Pair ease-enter and ease-exit for mount / unmount transitions; they're calibrated so the Kit's motion reads as one consistent vocabulary.

Shadow tokens

Five elevation steps plus two focus-ring shadows. The elevation shadows are theme-specific (dark uses heavy black layers; light leans on softer alphas).

:root,
[data-stridge-theme="dark"] {
    /* Elevation — xs for inline triggers up to xl for high-elevation reserve. */
    --stridge-kit-shadow-xs: 0 1px 1px 0 oklch(0% 0 0 / 0.125);
    --stridge-kit-shadow-sm: 0 4px 4px -1px oklch(0% 0 0 / 0.04), 0 1px 1px 0 oklch(0% 0 0 / 0.08);
    --stridge-kit-shadow-md: 0 3px 8px oklch(0% 0 0 / 0.125), 0 2px 5px oklch(0% 0 0 / 0.125);
    --stridge-kit-shadow-lg: 0 4px 40px oklch(0% 0 0 / 0.1), 0 3px 20px oklch(0% 0 0 / 0.125);
    --stridge-kit-shadow-xl: 0 4px 40px oklch(0% 0 0 / 0.14), 0 3px 20px oklch(0% 0 0 / 0.16);
}

:root {
    /* Focus halos — track the primary / destructive colors at 50% opacity. */
    --stridge-kit-shadow-ring-focus: 0 0 0 3px color-mix(in oklab, var(--stridge-kit-ring) 50%, transparent);
    --stridge-kit-shadow-ring-focus-destructive: 0 0 0 3px color-mix(in oklab, var(--stridge-kit-destructive) 50%, transparent);
}

Per-primitive namespaces

Beyond the role tokens, several primitives carry their own token namespace. Override these when you want one element type to diverge without touching the rest of the system.

:root,
[data-stridge-theme="dark"] {
    /* SelectableTile — asset rows, method tiles, pill chips */
    --stridge-kit-tile-surface: oklch(24.14% 0.01 285.67);
    --stridge-kit-tile-surface-selected: oklch(24.14% 0.01 285.67);
    --stridge-kit-tile-border: oklch(29.55% 0.01 285.72);
    --stridge-kit-tile-foreground: var(--stridge-kit-foreground);
    --stridge-kit-tile-active: var(--stridge-kit-primary);

    /* Details — key/value rows inside dialogs */
    --stridge-kit-details-surface: oklch(22% 0.01 285.58);
    --stridge-kit-details-border: oklch(29.55% 0.01 285.72);
    --stridge-kit-details-label: oklch(71.37% 0.02 261.32);
    --stridge-kit-details-value: oklch(95.57% 0 286.35);

    /* Card — modal frame, raised surfaces, recessed surfaces */
    --stridge-kit-card-surface: oklch(22% 0.01 285.58);
    --stridge-kit-card-surface-subdued: oklch(19.8% 0.01 285.46);
    --stridge-kit-card-surface-frame: oklch(20.15% 0.01 285.86);
    --stridge-kit-card-border: oklch(29.55% 0.01 285.72);
    --stridge-kit-card-border-frame: oklch(27.07% 0.01 285.77);
    --stridge-kit-card-shadow: 0 30px 80px oklch(0% 0 0 / 0.6), 0 2px 6px oklch(0% 0 0 / 0.4);

    /* Drawer grabber — the pill handle on the bottom-sheet surface */
    --stridge-kit-drawer-handle-color: oklch(52.95% 0.01 286.13 / 0.55);

    /* Skeleton — shimmer loading states */
    --stridge-kit-skeleton-base: oklch(29.55% 0.01 285.72);
    --stridge-kit-skeleton-highlight: oklch(35.16% 0.01 285.67);

    /* Attribution row — three-tier brand hierarchy */
    --stridge-kit-attribution-prefix-color: oklch(62% 0.005 286);
    --stridge-kit-attribution-mark-color: oklch(72% 0.005 286);
    --stridge-kit-attribution-wordmark-color: oklch(76% 0.005 286);
}

:root,
[data-stridge-scope] {
    /* Per-primitive radii — retuned in lockstep by every radius preset. */
    --stridge-kit-tile-radius-card: 12px;
    --stridge-kit-tile-radius-pill: 10px;
    --stridge-kit-details-radius: 14px;
    --stridge-kit-card-radius: 12px;
    --stridge-kit-card-radius-frame: 16px;
    --stridge-kit-button-radius: 10px;

    /* Dialog width cap — widen for full-page checkout integrations. */
    --stridge-kit-dialog-width: 480px;

    /* Drawer (bottom sheet) — width + height caps and grabber dimensions. The top corners reuse
       --stridge-kit-card-radius-frame (the bottom edge is flush against the container). */
    --stridge-kit-drawer-max-inline-size: var(--stridge-kit-dialog-width); /* width cap — defaults to the dialog width */
    --stridge-kit-drawer-max-block-size: calc(100% - 2rem);
    --stridge-kit-drawer-handle-size: 36px;       /* grabber width  */
    --stridge-kit-drawer-handle-thickness: 4px;   /* grabber height */
}

The drawer appears whenever a dialog resolves to "drawer" (a narrow container under "auto", or presentation="drawer"). Its surface reuses the Card frame chrome — --stridge-kit-card-surface-frame, --stridge-kit-card-border-frame, and --stridge-kit-card-radius-frame on the top corners — so a widget paints identically inside a dialog or a drawer; only the caps and grabber are drawer-specific. On a wide container the sheet caps at --stridge-kit-drawer-max-inline-size and centres rather than stretching edge-to-edge; the token defaults to --stridge-kit-dialog-width so the two surfaces match out of the box, but you can size the drawer apart from the dialog by overriding it. On a narrow container the sheet runs flush. The grabber colour is theme-paired (see the color block above). See Responsive presentation.

Worked examples

Match a brand color

<StridgeProvider appearance={{ accent: "oklch(78% 0.15 240)" }}>

Switch to sharp, dense terminal styling

<StridgeProvider appearance={{ theme: "dark", radius: "sharp", accent: "oklch(72% 0.18 280)" }}>

Light theme with bespoke surfaces

[data-stridge-theme="light"] {
    --stridge-kit-background: oklch(98% 0.005 250);
    --stridge-kit-card-surface: oklch(100% 0 0);
    --stridge-kit-card-surface-subdued: oklch(96% 0.005 250);
    --stridge-kit-border: oklch(92% 0.005 250);
}

Wider dialog for a checkout page

[data-stridge-scope] {
    --stridge-kit-dialog-width: 640px;
}

Next

Was this page helpful?