UI State Basic
Use global, derived, and scoped stores with bind-* directives.
Component
Card preview
Adjust the controls below to style this card live.
Theme
Accent
Radius
Shadow
Code
The following example assumes that you have imported the component and set up the theme.
<!-- Global scope: all stores shared across the page --><sv-ui-state global> <sv-store type="number" name="$hue" value="220"></sv-store> <sv-store type="number" name="$radius" value="8"></sv-store> <sv-store type="number" name="$elevation" value="1"></sv-store> <sv-store type="boolean" name="$dark" persistent value="false"></sv-store>
<!-- derive: recomputes whenever referenced stores change --> <sv-store derive="$radius + 'px'" name="$radiusLabel"></sv-store> <sv-store derive="$elevation === 0 ? 'Flat' : $elevation === 1 ? 'Soft' : 'Raised'" name="$elevationLabel"></sv-store></sv-ui-state>
<div class="card-explorer"> <!-- Live preview --> <!-- bind-class: sets 'is-dark' or 'is-light' as a dynamic class --> <!-- bind-style: sets color-scheme to drive all light-dark() CSS variables --> <div class="card-explorer-stage" bind-class="$dark ? 'is-dark' : 'is-light'" bind-style="color-scheme:$dark ? 'dark' : 'light'"> <!-- bind-attr-toggle: adds/removes the boolean data attribute based on truthiness --> <!-- bind-style: pushes CSS custom properties directly onto the element --> <div class="preview-card" bind-attr-toggle="data-elevated:$elevation > 0" bind-style=" --card-hue:$hue; --card-radius:$radiusLabel; --card-shadow:$elevation === 0 ? 'none' : $elevation === 1 ? '0 2px 12px oklch(0% 0 0 / 0.14)' : '0 8px 28px oklch(0% 0 0 / 0.22)' " >8 collapsed lines
<div class="preview-card-accent"></div> <div class="preview-card-body"> <p class="preview-card-eyebrow">Component</p> <h4 class="preview-card-title">Card preview</h4> <p class="preview-card-text">Adjust the controls below to style this card live.</p> <button type="button" class="preview-card-btn">Get started</button> </div> </div> </div>
<!-- Controls --> <div class="card-explorer-controls"> <!-- bind-class-toggle: adds 'is-active' when $dark is truthy --> <!-- bind-text: sets text content from an expression --> <!-- bind-event: runs expression on click --> <div class="control-row"> <span class="control-label">Theme</span> <button type="button" class="control-toggle" bind-class-toggle="is-active:$dark" bind-event="click:$dark(!$dark)" bind-text="$dark ? '🌙 Dark' : '☀️ Light'" ></button> </div>
<!-- bind-prop: keeps the DOM property (value) in sync with the store --> <!-- bind-event with .passive modifier: updates store on input without blocking scroll --> <!-- + coerces EVENT.target.value string to number --> <div class="control-row"> <span class="control-label">Accent</span> <input type="range" class="control-range" bind-event="input.passive:$hue(+EVENT.target.value)" bind-prop="value:$hue" max="360" min="0" step="1" /> <span class="control-swatch" bind-style="background:'oklch(55% 0.15 ' + $hue + ')'"></span> </div>
<div class="control-row"> <span class="control-label">Radius</span> <input type="range" class="control-range" bind-event="input.passive:$radius(+EVENT.target.value)" bind-prop="value:$radius" max="24" min="0" step="1" /> <!-- bind-text: shows the derived $radiusLabel store value --> <span class="control-value" bind-text="$radiusLabel"></span> </div>
<div class="control-row"> <span class="control-label">Shadow</span> <div class="control-btn-group"> <button type="button" class="control-btn" bind-class-toggle="is-active:$elevation === 0" bind-event="click:$elevation(0)"> Flat </button> <button type="button" class="control-btn" bind-class-toggle="is-active:$elevation === 1" bind-event="click:$elevation(1)"> Soft </button> <button type="button" class="control-btn" bind-class-toggle="is-active:$elevation === 2" bind-event="click:$elevation(2)"> Raised </button> </div> </div> </div>
<!-- Local scope: $open is isolated here and invisible to the rest of the page --> <sv-ui-state> <sv-store type="boolean" name="$open" value="false"></sv-store>
<div class="card-explorer-footer"> <!-- bind-attr: sets aria-expanded to "true" or "false" string (not a toggle) --> <button type="button" class="explorer-readout-btn" bind-attr="aria-expanded:$open" bind-event="click:$open(!$open)" bind-text="$open ? 'Hide values ↑' : 'Show CSS values ↓'" ></button>
<!-- bind-show: display:none when $open is falsy, restored when truthy --> <div class="explorer-readout" bind-show="$open"> <span class="readout-row"> --hue: <strong bind-text="$hue"></strong> </span>
<span class="readout-row"> border-radius: <strong bind-text="$radiusLabel"></strong> </span>
<span class="readout-row"> shadow: <strong bind-text="$elevationLabel"></strong> </span> </div> </div> </sv-ui-state></div>
<style>212 collapsed lines
.card-explorer { max-inline-size: 24rem; margin: auto; overflow: hidden; font-family: var(--sb-md-font); color: var(--sb-color-text); background: var(--sb-color-black); border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-md); }
.card-explorer-stage { display: flex; align-items: center; justify-content: center; padding: 2.5rem 1.5rem; background: var(--sb-color-gray-7); border-block-end: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); }
.preview-card { inline-size: 100%; max-inline-size: 15rem; overflow: hidden; background: var(--sb-color-black); border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); border-radius: var(--card-radius, 8px); box-shadow: var(--card-shadow, none); transition: border-radius 0.2s, box-shadow 0.2s;
& .preview-card-accent { block-size: 0.3rem; background: oklch(55% 0.15 var(--card-hue, 220)); }
& .preview-card-body { padding: 1.125rem; }
& .preview-card-eyebrow { margin: 0 0 0.2rem; font-size: var(--sb-text-2xs); font-weight: 600; color: oklch(55% 0.15 var(--card-hue, 220)); text-transform: uppercase; letter-spacing: 0.08em; }
& .preview-card-title { margin: 0 0 0.4rem; font-size: var(--sb-text-base); line-height: var(--sb-line-height-headings); color: var(--sb-heading-color); }
& .preview-card-text { margin: 0 0 0.875rem; font-size: var(--sb-text-sm); line-height: var(--sb-line-height); color: var(--sb-color-gray-3); }
& .preview-card-btn { padding: 0.35rem 0.875rem; font-family: inherit; font-size: var(--sb-text-sm); color: oklch(97% 0.01 var(--card-hue, 220)); cursor: pointer; background: oklch(55% 0.15 var(--card-hue, 220)); border: none; border-radius: calc(var(--card-radius, 8px) * 0.6); transition: filter 0.15s;
&:hover { filter: brightness(1.12); } } }
.card-explorer-controls { display: flex; flex-direction: column; gap: 0.75rem; padding: 1rem 1.25rem; border-block-end: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); }
.control-row { display: flex; gap: 0.75rem; align-items: center; }
.control-label { flex-shrink: 0; inline-size: 4rem; font-size: var(--sb-text-sm); color: var(--sb-color-gray-3); }
.control-range { flex: 1; accent-color: var(--sb-color-accent); cursor: pointer; }
.control-swatch { flex-shrink: 0; inline-size: 1.375rem; block-size: 1.375rem; border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-4); border-radius: var(--sv-bdr-rad-sm); transition: background 0.1s; }
.control-value { flex-shrink: 0; inline-size: 2.25rem; font-family: var(--sb-font-mono); font-size: var(--sb-text-xs); color: var(--sb-color-gray-3); text-align: end; }
.control-toggle { padding: 0.3rem 0.75rem; font-family: inherit; font-size: var(--sb-text-sm); color: var(--sb-color-text); cursor: pointer; background: var(--sb-color-gray-6); border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-sm); transition: background 0.15s;
&.is-active { background: var(--sb-color-gray-5); border-color: var(--sb-color-gray-4); } }
.control-btn-group { display: flex; gap: 0.25rem; }
.control-btn { padding: 0.3rem 0.75rem; font-family: inherit; font-size: var(--sb-text-sm); color: var(--sb-color-gray-3); cursor: pointer; background: var(--sb-color-gray-6); border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-sm); transition: background 0.15s, color 0.15s, border-color 0.15s;
&:hover:not(.is-active) { background: var(--sb-color-gray-5); }
&.is-active { color: var(--sb-color-accent-high); background: var(--sb-color-accent-low); border-color: var(--sb-color-accent); } }
.card-explorer-footer { padding: 0.75rem 1.25rem; }
.explorer-readout-btn { padding: 0; font-family: inherit; font-size: var(--sb-text-xs); color: var(--sb-color-gray-3); cursor: pointer; background: none; border: none;
&:hover { color: var(--sb-color-text); } }
.explorer-readout { display: flex; flex-direction: column; gap: 0.2rem; padding: 0.625rem 0.875rem; margin-block-start: 0.625rem; background: var(--sb-color-gray-7); border: var(--sv-bdr-sz-sm) solid var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-sm); }
.readout-row { font-family: var(--sb-font-mono); font-size: var(--sb-text-xs); color: var(--sb-color-gray-3);
& strong { font-weight: 600; color: var(--sb-color-accent-high); } }</style>