Theming
Simurgh separates raw heritage colors from semantic UI roles. Components consume semantic tokens, so applications can change the visual theme without editing component source or redefining the meaning of a palette color.
Import tokens
Section titled “Import tokens”Package consumers can import tokens without any component recipes:
@import '@simurgh-ui/styles/tokens.css';Each component stylesheet also includes the shared tokens. An application that imports only
@simurgh-ui/styles/button.css does not need a second token import. CLI consumers should load the
copied src/styles/simurgh/tokens.css file from the application’s global stylesheet entry.
CSS composition and import order
Section titled “CSS composition and import order”Load CSS in this order:
- The application reset or framework base layer.
- Simurgh tokens and component recipes.
- Application token, recipe, and utility overrides.
@import './reset.css';@import '@simurgh-ui/styles/dialog.css';@import './theme.css';@import './component-overrides.css';Simurgh does not ship a global reset and does not normalize every native element. The consuming
application remains responsible for global box-sizing, typography inheritance, body margin, and
other reset policy. Inspect native controls when switching between resets because browser defaults
can remain visible on components with deliberately minimal recipes.
Simurgh styles do not currently declare CSS cascade layers. Their normal selectors participate in the unlayered author cascade. If an application uses layers, place the imports into an explicit project layer and keep application overrides in a later layer:
@layer reset, vendor, app;
@import './reset.css' layer(reset);@import '@simurgh-ui/styles/dialog.css' layer(vendor);
@layer app { .simurgh-dialog { padding: 1.5rem; }}Use source order and intentional layers before increasing selector specificity or adding
!important. Component recipes generally use single classes and documented state attributes so an
application rule in the same layer and later source order can override them predictably.
Avoid redundant token imports
Section titled “Avoid redundant token imports”Choose one of these patterns per CSS entry:
- Import only the component stylesheets in use; each one includes
tokens.css. - Import
tokens.cssalone for fully headless components or application code that consumes tokens without recipes. - Import
all.cssonce when the application deliberately needs the complete recipe catalog.
Do not import tokens.css, recipes.css, all.css, and component stylesheets together by default.
That repeats declarations and makes source order harder to understand. Build tools may deduplicate
identical imports, but the application should not depend on tool-specific deduplication for correct
cascade behavior.
Tailwind
Section titled “Tailwind”Tailwind is optional; Simurgh requires no Tailwind plugin or preset. Put Simurgh CSS in the project’s vendor/component layer and application utilities in the intended later layer. Tailwind versions and project configurations differ in how generated utilities are layered, so confirm the built CSS order rather than assuming a utility always wins.
Use semantic tokens for application-wide brand decisions and utilities for local layout or composition. When a utility repeatedly fights a recipe selector, prefer a documented recipe or token override instead of escalating specificity at every call site.
Semantic token reference
Section titled “Semantic token reference”Color values are HSL channels rather than complete color functions. Use them as
hsl(var(--simurgh-primary)), including an optional alpha such as
hsl(var(--simurgh-primary) / 0.8).
| Token | Light default | Dark default | Role and contrast expectation |
|---|---|---|---|
--simurgh-background | 39 55% 97% | 222 47% 9% | Application canvas. Pair with foreground. |
--simurgh-foreground | var(--simurgh-ink) | 39 55% 92% | Primary text and icons. Must remain readable on background and surface. |
--simurgh-surface | 40 43% 99% | 222 40% 13% | Raised controls, cards, menus, and dialogs. Pair with foreground. |
--simurgh-muted | 39 31% 91% | 220 30% 20% | Subtle selected, hover, and secondary surfaces. |
--simurgh-muted-foreground | 217 20% 38% | 39 18% 70% | Secondary text. Must remain readable on background, surface, and muted. |
--simurgh-primary | var(--simurgh-persian-blue) | 176 57% 47% | Primary actions and emphasized states. Pair with primary-foreground. |
--simurgh-primary-foreground | 0 0% 100% | 222 47% 9% | Text and icons placed on primary. |
--simurgh-accent | var(--simurgh-saffron) | 42 88% 62% | Accent decoration or emphasis. Do not assume it is a text background without testing the chosen foreground. |
--simurgh-danger | var(--simurgh-pomegranate) | Inherits light value | Destructive actions and error emphasis. Pairings require contrast testing in both themes. |
--simurgh-border | 32 28% 78% | 219 29% 28% | Control and surface boundaries. Must remain perceivable next to adjacent surfaces. |
--simurgh-ring | var(--simurgh-firuzeh) | 176 57% 53% | Keyboard focus indicator. Must contrast with every surface on which focus can appear. |
--simurgh-radius | 0.625rem | Inherits light value | Shared corner radius for recipe surfaces and controls. |
--simurgh-shadow | 0 14px 40px rgb(15 23 42 / 0.16) | Inherits light value | Elevation shadow for floating and raised surfaces. |
--simurgh-duration | 160ms | Inherits light value | Shared transition duration; becomes 1ms under reduced-motion preference. |
Additional semantic roles keep component recipes and application utilities from overloading the core surface colors:
| Token family | Roles |
|---|---|
| Actions | secondary, secondary-foreground, danger-foreground |
| Status | success, success-foreground, warning, warning-foreground, information, information-foreground |
| Controls | input-surface, input-border, disabled-surface, disabled-foreground, hover, pressed |
| Layers | scrim, shadow-sm, shadow, shadow-lg |
| Sizing | control-sm (32px), control-md (40px), control-lg (48px), space-1 through space-4 |
| Typography | font-sans, text-sm, text-md, text-lg, line-height |
Foreground roles are mandatory partners for their named backgrounds. Status colors communicate emphasis, not meaning by themselves; retain text or icon labels. Control sizes are minimum block sizes and may grow for wrapped or localized content.
“Inherits light value” means the dark selector does not override that token; normal CSS inheritance
keeps the :root value.
Changing a semantic pair transfers responsibility for its contrast to the application. At minimum,
test foreground on background and surface, muted-foreground on its supported surfaces,
primary-foreground on primary, focus ring against every focusable surface, and border against
both sides of the boundary. Test light, dark, forced-colors, disabled, hover, focus, and selected
states rather than checking isolated swatches.
Three customization levels
Section titled “Three customization levels”Choose the least invasive level that meets the design requirement. The levels can be combined, but each one transfers more styling responsibility to the application.
1. Override semantic tokens
Section titled “1. Override semantic tokens”Use tokens for brand colors, corner shape, elevation, and motion while retaining the shipped component recipes:
@import '@simurgh-ui/styles/dialog.css';
:root { --simurgh-primary: 265 72% 44%; --simurgh-ring: 265 82% 55%; --simurgh-radius: 1rem;}This is the preferred application-wide customization level. Behavior, DOM, recipe selectors, and state presentation remain owned by Simurgh.
2. Override recipe classes and state hooks
Section titled “2. Override recipe classes and state hooks”Load the component recipe, then add application rules after it. Dialog recipes expose classes such
as .simurgh-overlay, .simurgh-content, and .simurgh-dialog; interactive components also expose
documented ARIA and data-* state attributes.
@import '@simurgh-ui/styles/dialog.css';
.simurgh-dialog { border-block-start: 0.25rem solid hsl(var(--simurgh-accent)); padding: 1.5rem;}
.simurgh-overlay[data-state='open'] { backdrop-filter: blur(0.25rem);}Only target classes, attributes, and DOM parts documented on the component page. Internal element order and undocumented selectors are not a stable styling contract during the pre-release period.
3. Provide fully headless styling
Section titled “3. Provide fully headless styling”Omit the component recipe when the application will provide every visual state. Keep tokens only if the application still wants the shared semantic system:
import { Dialog, DialogContent, DialogOverlay, DialogPortal, DialogTrigger,} from '@simurgh-ui/react/dialog';import '@simurgh-ui/styles/tokens.css';import './account-dialog.css';
export function AccountDialog() { return ( <Dialog> <DialogTrigger className="account-dialog-trigger"> Edit account </DialogTrigger> <DialogPortal> <DialogOverlay className="account-dialog-overlay" /> <DialogContent className="account-dialog-content" aria-label="Edit account" > Account fields </DialogContent> </DialogPortal> </Dialog> );}Headless styling removes only the optional visual recipe; component behavior and accessibility logic still run. The application must style default, hover, focus-visible, active, open/closed, disabled, invalid, loading, reduced-motion, forced-colors, dark, and RTL states that apply. CLI consumers may also edit the copied markup and classes because that source is application-owned.
Override semantic roles
Section titled “Override semantic roles”Declare application overrides after importing Simurgh styles:
:root { --simurgh-primary: var(--simurgh-persian-blue); --simurgh-accent: var(--simurgh-saffron); --simurgh-ring: var(--simurgh-firuzeh); --simurgh-radius: 0.375rem;}Prefer semantic overrides to changing raw heritage tokens. A raw token such as
--simurgh-persian-blue describes a color; --simurgh-primary describes how the interface uses a
color.
Dark mode
Section titled “Dark mode”Dark tokens activate when .dark or data-theme="dark" appears on the component or any ancestor:
<html data-theme="dark"> <!-- application --></html>Override dark roles under the same selector after the Simurgh import:
.dark,[data-theme='dark'] { --simurgh-background: 225 32% 8%; --simurgh-surface: 225 28% 12%; --simurgh-foreground: 40 35% 94%;}Complete custom theme example
Section titled “Complete custom theme example”The downloadable light/dark theme overrides every semantic color, shape, shadow, and motion token and preserves the reduced-motion override. Load it after a Simurgh component recipe:
@import '@simurgh-ui/styles/dialog.css';@import '/examples/custom-theme.css';The documentation build validates the semantic token surface and its documented theme behavior. It also checks 20 WCAG contrast pairings: normal text pairs at a minimum of 4.5:1 and focus-ring and border boundaries at a minimum of 3:1. These automated checks cover the declared pairings, not every component state; keyboard focus, forced colors, disabled states, and application overrides still require browser testing.
Raw heritage palette
Section titled “Raw heritage palette”The shipped palette exposes firuzeh, persian-blue, lapis, cobalt, brick, ochre,
saffron, pomegranate, ivory, and ink under the --simurgh-* prefix. These are source colors
for semantic mapping, not guaranteed foreground/background pairs.
The palette is informed by Iranian turquoise and cobalt tile glazing, lapis and ultramarine decoration, ochre pigments and brick, and strong blue/yellow and red/white contrasts. Semantic mappings are adjusted where necessary to retain readable interface contrast.
Motion and RTL
Section titled “Motion and RTL”--simurgh-duration becomes 1ms under prefers-reduced-motion: reduce. Preserve that behavior in
custom animation rules and avoid introducing essential information through motion alone.
Recipes use logical CSS properties so dir="rtl" does not require a separate stylesheet. Use
logical properties such as padding-inline, margin-inline-start, and inset-inline-end in
application overrides to preserve that behavior.
Component customization patterns
Section titled “Component customization patterns”Simurgh does not impose a universal size or variant prop. Native attributes and
behavioral props belong to the adapter API; visual choices belong to application classes, stable
parts, state attributes, and semantic tokens. This keeps copied and headless components open to an
application’s own design vocabulary without promising a variant API that a component does not
implement.
Sizes and density
Section titled “Sizes and density”Scope density on an ancestor instead of repeating overrides on every child. The shipped modes use logical dimensions and affect interactive controls rather than content surfaces:
| Mode | Control target | Menu/item target | Intended use |
|---|---|---|---|
comfortable | 44px | 40px | Touch-friendly forms, dialogs, and primary application flows |
compact | 40px | 36px | Default mixed-input desktop and responsive interfaces |
dense | 32px | 28px | Data-heavy expert tools with an alternative spacious/touch mode |
<form data-density="comfortable">...</form><div class="data-grid-tools" data-density="dense">...</div>compact matches the default 40px control rhythm. Dense mode remains above WCAG’s 24px minimum
target size, but proximity and motor-access needs still matter; do not make it the only mode for a
touch-first product. Override --simurgh-control-height, --simurgh-control-padding, and
--simurgh-item-height when product research supports a different scoped policy.
Content-sized surfaces such as Dialog, Popover, Sheet, Sidebar, and Tooltip already use viewport
relative maximum sizes. Override their documented recipe class with min(), max(), clamp(), and
logical dimensions rather than relying on internal wrappers.
Visual variants
Section titled “Visual variants”Create application variants with a class or a project-owned data-variant attribute. Forward it to
the same root element identified by the component page’s attribute-forwarding and styling-contract
sections:
<Button className="billing-action" data-variant="danger"> Delete payment method</Button>.billing-action[data-variant='danger'] { background: hsl(var(--simurgh-danger)); color: white;}Do not reuse behavioral attributes such as data-state, aria-expanded, or aria-invalid as a
visual variant switch. They describe component state and must retain their documented meaning.
Icons are consumer content; Simurgh does not require an icon package. Decorative icons need
aria-hidden="true", while an icon-only control needs an accessible name. Use currentColor, a
logical gap, and a fixed flex size so the icon follows disabled, danger, dark, and high-contrast
foreground styles:
<Button className="button-with-icon"> <TrashIcon aria-hidden="true" focusable="false" /> Delete</Button>
<Button aria-label="Close dialog" className="icon-button"> <CloseIcon aria-hidden="true" focusable="false" /></Button>.button-with-icon { display: inline-flex; gap: 0.5rem; align-items: center;}.button-with-icon > svg,.icon-button > svg { inline-size: 1em; block-size: 1em; flex: none;}.icon-button { inline-size: 2.5rem; padding-inline: 0;}Responsive behavior
Section titled “Responsive behavior”Prefer container queries when a component responds to its placement and media queries when the whole viewport changes the interaction. Preserve DOM order, keyboard order, labels, and component state across breakpoints; CSS may rearrange presentation, but it should not create a second hidden interactive copy.
.profile-card { container-type: inline-size;}.profile-card__actions { display: flex; gap: 0.75rem;}
@container (inline-size < 24rem) { .profile-card__actions { flex-direction: column; } .profile-card__actions > * { inline-size: 100%; }}For overlays, test narrow and short viewports, browser zoom, virtual keyboards, and long translated
labels. Use 100dvh only with a suitable fallback when older target browsers are supported.
Animation and reduced motion
Section titled “Animation and reduced motion”Build optional transitions from --simurgh-duration and the component’s documented state hooks.
Reduced motion should remove spatial movement, repeated animation, parallax, and long fades rather
than merely making them slightly faster:
.simurgh-dialog { transition: opacity var(--simurgh-duration) ease, transform var(--simurgh-duration) ease;}
.simurgh-dialog[data-state='closed'] { opacity: 0; transform: translateY(0.5rem);}
@media (prefers-reduced-motion: reduce) { .simurgh-dialog { transition: none; } .simurgh-dialog[data-state='closed'] { transform: none; }}Never delay focus movement, form feedback, or dismissal until an animation finishes. When replacing recipe CSS, retain state semantics even if no animation is rendered.
Dark mode and RTL
Section titled “Dark mode and RTL”Use .dark or [data-theme='dark'] for theme activation and dir="rtl" on the document or the
nearest independently directed subtree. Prefer semantic-token overrides for dark mode; avoid
per-component color inversions that bypass contrast pairs. Prefer logical properties and directional
icons. Mirror arrows that communicate previous/next or physical direction, but do not mirror logos,
media playback icons, checkmarks, or text-direction-independent symbols.
.next-icon { transform: rotate(0deg);}[dir='rtl'] .next-icon { transform: rotate(180deg);}What applies where
Section titled “What applies where”| Concern | Most relevant components | Recommended surface |
|---|---|---|
| Size and density | Buttons, fields, menus, toolbars, pagination, tags | Root class plus project-owned density ancestor |
| Visual variants | Actions, badges, alerts, cards, feedback states | Project-owned class or data-variant |
| Icons | Buttons, menu items, fields, tree items, navigation | Consumer content using currentColor and accessible naming |
| Responsive layout | Dialog, Sheet, Drawer, Sidebar, tables, forms, toolbars | Logical dimensions, container/media queries, stable DOM order |
| Animation | Dialog, Drawer, Sheet, Popover, Tooltip, Toast, disclosure components | Documented data-state or ARIA hooks and --simurgh-duration |
| Dark mode | Every visible recipe | Semantic tokens under .dark or [data-theme='dark'] |
| RTL | Every directional layout; especially Sheet, Dialog, Tree, navigation | dir, logical CSS properties, and selectively mirrored icons |
Test customized components in their default, hover, focus-visible, active, disabled, invalid, loading, open/closed, light/dark, LTR/RTL, reduced-motion, forced-colors, zoomed, and narrow-viewport states where those states apply.
Last verified on 2026-08-13 against Simurgh registry 0.1.1.