Skip to content

css

Emits a styles.css file for each component. Each anatomy element becomes a CSS selector; token references become var(--) declarations; variant props become [data-*] attribute selectors. Boolean props use presence selectors ([data-prop]); string-valued props use value selectors ([data-prop="value"]).

Use When

  • You want a baseline stylesheet that mirrors the component’s Figma token application.
  • You want to wire up tokens in CSS without manually translating spec values.
  • You want attribute-scoped variant overrides that align with your component’s data attributes.

Invocation

Terminal window
specs transform css

Output

Each component subfolder receives a styles.css file. When subcomponents are present, each also receives a styles.css inside its own named subfolder — see Subcomponent Output below.

Example Output

An Alert component with severity and dismissible variant props, and anatomy elements root, icon, and body:

/* Generated. Do not edit — regenerate with `specs transform`. */
.ds-alert {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-4) var(--space-6);
background: var(--color-surface-neutral);
border-radius: var(--radius-lg);
}
.ds-alert__icon {
flex-shrink: 0;
width: var(--size-icon-md);
height: var(--size-icon-md);
fill: var(--color-icon-neutral);
}
.ds-alert__body {
flex: 1;
color: var(--color-text-primary);
font: var(--font-body-sm);
}
/* Variant: severity */
.ds-alert[data-severity="warning"] {
background: var(--color-surface-warning);
}
.ds-alert[data-severity="warning"] .ds-alert__icon {
fill: var(--color-icon-warning);
}
.ds-alert[data-severity="error"] {
background: var(--color-surface-error);
}
.ds-alert[data-severity="error"] .ds-alert__icon {
fill: var(--color-icon-error);
}
/* Variant: dismissible */
.ds-alert[data-dismissible] {
padding-inline-end: var(--space-10);
}
/* Compound variant: severity + dismissible */
.ds-alert[data-severity="error"][data-dismissible] .ds-alert__icon {
fill: var(--color-icon-error-strong);
}

Root element selectors use the component’s kebab-cased name. Child elements use the __element BEM suffix. Variants use [data-propName] presence selectors for boolean props and [data-propName="value"] value selectors for string enum props, with camelCase prop names kebabized. Compound selectors combine multiple variant attributes for intersection overrides.

Token Resolution

Token references are resolved to CSS var(--) based on config.format.tokens:

FormatResolution
TOKEN / TOKEN_NAME / FIGMA_NAME / TOKEN_FIGMA_EXTENSIONSPath-derived kebab variable: Color/Surface/Neutralvar(--color-surface-neutral)
FIGMA_SYNTAX_WEBSpec value is already the CSS var name — used verbatim
CUSTOMUses $cssVar field if present, otherwise path derivation
FIGMA_SYNTAX_IOS / FIGMA_SYNTAX_ANDROIDPath derivation fallback

Config

No transformer-specific options. Token format comes from config.format.tokens. Selector strategy for variant props comes from config.processing.states.

config:
format:
tokens: TOKEN # controls how token vars are named
processing:
states: # optional — omit to keep data-* attribute selectors
hover:
prop: state
value: hover
disabled:
prop: isDisabled
transformers:
- name: css

When processing.states is absent, all variant props produce [data-*] selectors (the default shown in the example above). When present, classified props emit semantic CSS pseudo-classes and ARIA attribute selectors instead.

Disabled guard on hover and active

When the disabled concept is configured in processing.states, the transformer automatically appends :not(:disabled):not([aria-disabled="true"]) to every :hover and :active selector — including compound variants that mix a data attribute with :hover or :active. This prevents hover and active styles from firing on disabled elements without any extra CSS to write.

/* disabled concept configured → hover and active are guarded */
.ds-button:hover:not(:disabled):not([aria-disabled="true"]) { … }
.ds-button:active:not(:disabled):not([aria-disabled="true"]) { … }
/* data attribute + :hover compound variant also receives the guard */
.ds-button[data-variant="primary"]:hover:not(:disabled):not([aria-disabled="true"]) { … }

Selectors that are not :hover or :active (e.g. :focus-within, :disabled itself) are never guarded.

Subcomponent Output

When subcomponents are present in variants.yaml, each subcomponent gets its own styles.css inside a named subfolder. The subfolder name is the subcomponent’s key in the spec (e.g. group, item). The BEM root class is scoped to the subcomponent’s kebab-cased key, not the parent component.

dsActionList/
styles.css ← parent component stylesheet (.ds-action-list)
group/
styles.css ← subcomponent stylesheet (.group)
item/
styles.css ← subcomponent stylesheet (.item)

The subcomponent styles.css follows the same structure as the parent — default element blocks first, then variant blocks — but BEM selectors are scoped to the subcomponent’s own kebab-cased key, not the parent component name:

/* Generated. Do not edit — regenerate with `specs transform`. */
.group {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.group__label {
color: var(--color-text-secondary);
font: var(--font-label-xs);
}
/* Variant: divider */
.group[data-divider] {
border-top: 1px solid var(--color-border-subtle);
}

Subcomponent stylesheets are fully self-contained — elements and variants from the parent component never appear in them. Configure subcomponent discovery in config.processing.subcomponents.

See Also