Data-Attribute State System
Interactive states are driven by data-st-* attributes — no JS framework needed. Attributes are never removed once present; their value changes instead (e.g. data-st-active="true" → data-st-active="false"). CSS targets attribute values, not class toggling.
Toggling the attribute is your job — a line of JS like el.setAttribute('data-st-visible', 'true') — Strata only supplies the CSS that reacts to the value once it changes. The exception is Strata's own component packages: @strata-packages/modal, @strata-packages/offcanvas, @strata-packages/skeleton-loader, and the built-in accordion/dropdown/toast markup already toggle these attributes for you internally — you only reach for the raw pattern below when building your own interactive element from scratch.
Not gated by the JIT scanner
safelist them). These attribute selectors are different — they're declared directly under @layer st-base in src/layers/base.js, which is always part of the output. You never need to write data-st-visible="true" anywhere in your source for the CSS to exist; it's there unconditionally, the same way the reset and theme tokens are.| Attribute | Values | Effect |
|---|---|---|
data-st-visible | "true" / "false" | Fade in/out (opacity + visibility + transform) |
data-st-collapsed | "true" / "false" | Expand/collapse (max-height + opacity) |
data-st-active | any | Adds transition to color/bg/border/shadow/transform |
data-st-loading | "true" | opacity 0.7, pointer-events none, cursor wait |
data-st-disabled | "true" | opacity 0.5, pointer-events none, cursor not-allowed |
data-st-skeleton | "true" / "false" / "null" | Shimmer overlay (managed by skeleton plugin) |
data-st-shake | "true" | One-off shake animation on .modal-dialog (invalid-submit feedback) |
data-st-side | "left" / "right" / "top" / "bottom" | Which edge .offcanvas slides in from |
data-st-visible
Fades and slides an element in or out instead of an instant show/hide. Backs modals, toasts, and dropdown menus out of the box.
data-st-visible="true"<div data-st-visible="true">
Toggle me
</div>el.setAttribute('data-st-visible', 'true') // show
el.setAttribute('data-st-visible', 'false') // hidedata-st-collapsed
Expands or collapses an element via max-height + opacity — what accordions and collapsible panels use under the hood.
overflow: hidden and max-height: 0.data-st-collapsed="false"<div data-st-collapsed="false">
Panel content — present in the DOM even while collapsed.
</div>el.setAttribute('data-st-collapsed', 'false') // expand
el.setAttribute('data-st-collapsed', 'true') // collapsedata-st-loading / data-st-disabled
Both dim the element and swap the cursor — loading uses wait, disabled uses not-allowed. Either one also sets pointer-events: none, so clicks don't need a separate guard in your handler.
data-st-loading="false"<button class="btn-primary" data-st-loading="true">Submit</button>data-st-disabled="false"<button class="btn-secondary" data-st-disabled="true">Delete</button>data-st-active
Unlike the others, data-st-active has no baked-in visual effect on its own — it only adds a transition for color/background/border/shadow/transform, so whatever style change you drive with your own CSS (or an inline style) animates smoothly instead of snapping. The attribute's value can be anything; only its presence matters.
/* data-st-active only adds the transition — you supply the actual state styles */
.my-widget[data-st-active="true"] {
background: var(--st-primary);
}data-st-skeleton
Renders a shimmering placeholder overlay in place of real content. The CSS ships in core Strata, but the plugin that manages toggling it on and off based on your data-loading state — "true" / "false" / "null" — is a separate install.
Loaded headline text
and a shorter line under it.
data-st-skeleton="true"<div data-st-skeleton="true">
<p>This text is replaced by a shimmer bar until data arrives.</p>
</div>Requires @strata-packages/skeleton-loader for the JS side
[data-st-skeleton] CSS (shimmer keyframe, overlay, per-element-type handling for links/buttons/media) lives in core Strata and works with a bare data-st-skeleton="true" in your markup. Automatically wiring that attribute up to real async state — swapping it to "false" once data arrives — is what @strata-packages/skeleton-loader does.data-st-shake
Component-specific rather than generic — it's only wired up on .modal-dialog, as feedback for "you clicked the backdrop / pressed Esc on a modal that won't dismiss." It's momentary, not a persistent state like the attributes above: set it to "true", let the 0.25s animation play, then remove it (or set it back to "false") so it can retrigger next time.
data-st-shake="false"<div class="modal" aria-hidden="false" data-st-shake="true">
<div class="modal-dialog">…</div>
</div>modal.setAttribute('data-st-shake', 'true')
setTimeout(() => modal.removeAttribute('data-st-shake'), 250) // matches the 0.25s animationdata-st-side
Sets which edge an .offcanvas panel slides in from. Unlike the state attributes above, this one isn't toggled at runtime to animate anything by itself — it's set once (usually straight in the markup) to configure the panel, and aria-hidden is what actually opens/closes it.
data-st-side="left"<div class="offcanvas" data-st-side="right" aria-hidden="true">
<div class="offcanvas-header">…</div>
<div class="offcanvas-body">…</div>
</div>