strata
DocsPackagesBlogsShowcase
Introduction

Guides

  • Installation
  • Configuration
  • Build Pipeline
  • Theme System
  • Data-Attribute States
  • Versioning & Contributing

Utilities

  • Spacing
  • Display
  • Flexbox
  • Grid
  • Sizing
  • Typography
  • Colors
  • Borders
  • Shadows
  • Position
  • Overflow
  • Opacity & Visibility
  • Misc
  • Arbitrary Values

Components

  • Buttons
  • Cards
  • Forms
  • Navigation
  • Modals
  • Alerts, Badges & Progress
  • Tables & Lists
  • Dropdowns, Tooltips & Popovers
  • Accordion & Offcanvas
  • Placeholders

Theme System

Themes are set via data-st-theme on any ancestor element (usually <html>). Without it, prefers-color-scheme is respected automatically. Try the theme switcher in the header — it drives this exact attribute.

<html data-st-theme="dark">   <!-- dark theme -->
<html data-st-theme="dim">    <!-- dim theme -->
<html data-st-theme="light">  <!-- explicit light -->

npx strata-css init locks you to light theme — on purpose, but easy to miss

The installer's layout injection adds data-st-theme="light" to your <html> tag if the attribute isn't already present. That silently overrides the prefers-color-scheme auto-detection described above — dark-mode users won't get a dark theme until you either remove that attribute or wire up switching yourself, below. init has no concept of a theme toggle; it only sets a static starting value.

Switching themes in JS

document.documentElement.setAttribute('data-st-theme', 'dark')

Preventing flash on load

Read the stored preference before hydration so the theme attribute is set before first paint:

<script>
  const t = localStorage.getItem('theme')
  if (t) document.documentElement.setAttribute('data-st-theme', t)
</script>

This site's implementation

See app/layout.tsx — an inline script runs in <head> before the body renders, and components/ThemeToggle.tsx writes to localStorage and the attribute on click.

Tokens

Every value a theme could plausibly change lives in one place: a set of CSS custom properties (--st-*) declared under @layer st-base. Components never hardcode a color or a shadow — they read one of these variables instead. That's the entire trick behind theming: change what --st-primary points to, and every button, link and focus ring using it updates at once, with zero component-level code.

Not every token changes between themes, though — some are genuinely theme-aware (a different value per light/dark/dim), and some are just shared constants that happen to live in the same block. Both kinds are listed below, grouped by what they're for, with which is which called out per group — plus the actual default value each one ships with, so you know what you're changing before you change it.

Brand colors — theme-aware

The core palette. Each has a plain and a -hover variant, and every one of them takes a different value in dark and dim mode — that's most of what "dark mode" actually is, under the hood.

TokenLight (default)DarkDimUsed for
--st-primary#0d6efd#6ea8fe#90cdf4Your brand color — buttons, links, focus rings, active states.
--st-primary-hover#0b5ed7#8bb9fe#a8d8f6
--st-secondary#6c757d#a0a7ae#9ca3afThe muted counterpart — outline buttons, less prominent actions.
--st-secondary-hover#5c636a#b4bac0#b0b7be
--st-success#198754#75b798#86efacPositive states — success alerts, valid form fields.
--st-success-hover#157347#8ecbaf#9af2bc
--st-danger#dc3545#ea868f#fca5a5Errors and destructive actions — delete buttons, invalid fields.
--st-danger-hover#bb2d3b#f1a1a8#fdb7b7
--st-warning#ffc107#ffda6a#fcd34dCaution states — warning alerts and badges.
--st-warning-hover#ffca2c#ffe083#fdda65
--st-info#0dcaf0#6edff6#67e8f9Neutral informational states — info alerts and badges.
--st-info-hover#31d2f2#88e5f7#80ecfa
--st-light#f8f9fa#f8f9fa#374151A light neutral — text/icons that need to stay light even in dark mode (e.g. on a colored badge).
--st-dark#212529#adb5bd#d1d5dbThe dark counterpart to --st-light.

Surfaces & text — theme-aware

The page's actual background and text colors — these are what make dark mode look dark, independent of the brand colors above.

TokenLight (default)DarkDimUsed for
--st-bg#ffffff#212529#2d3748The page background — <body> reads this directly.
--st-bg-secondary#f8f9fa#2b3035#374151A slightly offset surface — cards, code blocks, anything above the page background.
--st-text#212529#dee2e6#e2e8f0The default body text color.
--st-text-muted#6c757d#8c959e#9ca3afDimmer text — captions, helper text, anything secondary.
--st-border#dee2e6#495057#4a5568The default border color used across components.

Two more live in this same group but don't change with theme — --st-border-radius (default 0.375rem) and --st-border-width (default 1px) control the shape of borders, not their color, so light/dark/dim all share the same value.

Typography — constant across all themes

TokenDefaultUsed for
--st-font-family-apple-system, …, sans-serifThe default font stack for the whole page — the system font, not a webfont.
--st-font-size-base1remThe base body text size everything else scales from.
--st-line-height-base1.5The default line height for body text.
--st-font-weight-base400The default weight for body text.
--st-heading-weight600The weight used for h1–h6.

Shadows — theme-aware

Same three shadow sizes in every theme, but dark and dim use a heavier, more opaque shadow — a shadow tuned for a white background barely reads against a dark one.

TokenLight (default)DarkDimUsed for
--st-shadow-sm0 0.125rem 0.25rem rgba(0,0,0,.075)rgba(0,0,0,.3)rgba(0,0,0,.25)A subtle shadow — small elements, inputs.
--st-shadow0 0.5rem 1rem rgba(0,0,0,.15)rgba(0,0,0,.5)rgba(0,0,0,.4)The default shadow — cards, dropdowns.
--st-shadow-lg0 1rem 3rem rgba(0,0,0,.175)rgba(0,0,0,.6)rgba(0,0,0,.5)A pronounced shadow — modals, popovers, anything that should feel like it's floating.

The dark/dim columns above show only the opacity, not the full offset/blur — those stay identical to the light value in every theme; only how dark and opaque the shadow itself is changes.

Transitions — constant across all themes

Speeds and easing curves for animation. -theme variants exist specifically for the color/background swap when the theme itself changes, kept separate so you can tune "how fast does the whole page fade to dark" independently of every other transition on the site.

TokenDefaultUsed for
--st-duration200msThe default transition duration.
--st-duration-fast100msQuicker alternative.
--st-duration-slow400msSlower alternative.
--st-duration-theme150msHow long a theme switch itself takes to fade in.
--st-easingcubic-bezier(.4,0,.2,1)The default easing curve.
--st-easing-incubic-bezier(.4,0,1,1)Ease-in variant.
--st-easing-outcubic-bezier(0,0,.2,1)Ease-out variant.
--st-easing-themecubic-bezier(.22,1,.36,1)The easing curve used specifically for theme-switch transitions.
--st-transitionall var(--st-duration) var(--st-easing)Ready-made shorthand built from the tokens above.
--st-transition-fastall var(--st-duration-fast) var(--st-easing)The quicker version of the same shorthand.

Z-index scale — constant across all themes

A shared stacking order so a dropdown, a sticky header and a modal never fight for the same layer by accident.

TokenDefault
--st-z-dropdown1000
--st-z-sticky1020
--st-z-fixed1030
--st-z-modal-backdrop1040
--st-z-modal1050
--st-z-tooltip1060
--st-z-toast1070

Focus ring — theme-aware

TokenLight (default)DarkDim
--st-focus-ring0 0 0 .25rem rgba(13,110,253,.25)rgba(110,168,254,.25)rgba(144,205,244,.25)

The box-shadow every interactive element gets on keyboard focus (:focus-visible). It's tinted to match --st-primary in each theme, so it stays visible without ever clashing.

Skeleton loader

Used by the shimmer loading placeholder (data-st-skeleton). The colors are theme-aware, timing and shape aren't:

--st-skeleton-basetheme-aware#e2e8f0 light · #2d3748 dark · #374151 dimThe shimmer's resting background color.
--st-skeleton-shinetheme-aware#f8fafc light · #4a5568 dark · #4b5563 dimThe lighter color the shine sweep animates through.
--st-skeleton-durationconstant1.5sHow long one shimmer sweep takes.
--st-skeleton-radiusconstant4pxCorner rounding for skeleton bars — separate from --st-border-radius so you can tune it independently.

Spacing — constant across all themes

TokenDefaultUsed for
--st-gutter-x1.5remThe default column gutter width for the grid system.
--st-gutter-y0The default column gutter height (row gap) for the grid system.

See the spacing utilities for the rest of the spacing scale, which is applied directly as utility values rather than through tokens.

Not covered here: per-component override tokens

Every token above lives under @layer st-base and is what actually changes when the theme switches. A separate set — --st-btn-color, --st-badge-color, --st-table-dark-bg, --st-tooltip-bg, --st-nav-pills-active-color, and about a dozen more — lives inside individual component rules instead. Those don't vary by theme; they're customization hooks for one button, one card, one instance (.btn-primary { --st-btn-bg: gold; }), not part of the theme system itself.
PreviousBuild PipelineNextData-Attribute States
strata

JIT CSS Framework. Built for modern UI.

Docs

  • Introduction
  • Installation
  • Configuration
  • Utilities

Packages

  • All Packages
  • Forms
  • Modal
  • Chart

GitHub

  • Repository
  • Issues
  • Pull Requests
  • Discussions

npm

  • strata-css
  • Releases
  • Changelog
LicenseContributingChangelog
PrivacyTerms