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.

Tip

Don't hand-tune tokens blind — Kitweak, the live theme editor on the demo site, drives every token on this page against real Kit surfaces. Tweak color, typography, radius, spacing, shadow, and motion tokens, check contrast as you go, then export the result as CSS custom properties, a <StridgeProvider appearance> snippet, or re-importable JSON — the export matches the live preview exactly.

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?