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
| Path | When | Where 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 overrides | Brand-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:
theme→data-stridge-theme="<value>"— swaps the entire color palette.accent→style="--stridge-kit-primary: <value>"— every CTA / focus ring / progress affordance rebinds in one place.radius→data-stridge-radius="<value>"— retunes the base radius token; per-primitive radii scale with it.direction→dir="…"on the scope plusdata-stridge-flip-on-rtlmarkers 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 (default600). 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.
| Preset | Value | Feel |
|---|---|---|
sharp | 0px | Squared edges; common in trading terminals. |
subtle | 6px | Light softening. |
rounded | 12px (default) | Default — balances softness with information density. |
pill | 9999px | Fully 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
- Data attributes & slots — target individual sub-parts with CSS without forking.
- Internationalization — locale-conditional fonts, RTL.
- Provider — every
appearancefield with its default.