Customizing

Every design decision in the framework is a CSS variable or a Sass function. Nothing is hardcoded at a call site.

Variable reference

VariableDefaultPurpose
--colorrebeccapurpleBase hue. Everything derives from this.
--accent-colorcomplement of --colorLinks
--error-colormaroonInvalid inputs, small.error
--fallback-colorwhiteUsed by .contrast
--border-radius0.25remCorners on every surface
--border-width0.1625remBorders and rules
--padding1remBase spacing unit
--box-shadowfour-layer shadowArticles and dialogs
--icon-width1remSize of input icons
--icon-position0.75remInset of input icons
--surface-lc-1-9derivedBackground stops, light to dark
--text-lc-1-9derivedForeground stops, high to low contrast
--primary-surface-lcstop 6Primary button background
--primary-text-lcstop 1Primary button text
--grey-surface-lc-*derivedChroma-free equivalents, used by disabled states

--primary-color is declared on :root but is not read by any rule. Primary buttons use --primary-surface-lc against the ambient --color.

Scoping

Variables cascade, so overriding one on a subtree is the whole customization story. No build step is involved in any of this.

Scoped surface

Different hue, different radius, same markup.

<article class="card" style="--color: seagreen; --border-radius: 1rem">
  <h3>Scoped surface</h3>
  <p>Different hue, different radius, same markup.</p>
  <button>Themed button</button>
</article>

Color scheme

Without a data-color-scheme attribute, the scheme follows prefers-color-scheme. Set the attribute to pin it, on <html> or on any subtree.

Forced light

Forced dark

<div class="flex flip">
  <div data-color-scheme="light" class="scheme-pane">
    <p><strong>Forced light</strong></p>
    <button>Button</button>
  </div>
  <div data-color-scheme="dark" class="scheme-pane">
    <p><strong>Forced dark</strong></p>
    <button>Button</button>
  </div>
</div>

Replacing the scale

The nine stops live in $lcArray in vars.scss, as (lightness, chroma) pairs from lightest to darkest. $dimLcArray is the greyscale counterpart used by disabled states.

$lcArray: (
  (99%, 0.018),
  (90.5%, 0.045),
  (80.5%, 0.125),
  (69.5%, 0.17),
  (58%, 0.185),
  (46%, 0.17),
  (34%, 0.135),
  (24.5%, 0.095),
  (17%, 0.055)
);

If you change these, check contrast in both schemes. Light mode resolves text-lc-N on surface-lc-M to stops (M, 10 - N); dark mode resolves the same pairing to stops (10 - M, N). Those are different swatches, so passing in one scheme does not imply passing in the other.

Sass API

SignatureReturns
cur-color($lc)The ambient --color at stop $lc
get-color($lc, $color)An explicit color at stop $lc
get-grey($lc, $color)The same, with chroma forced to zero
get-border-color()Shorthand for cur-color(var(--surface-lc-4))
oklch-from-vars($color, $lightness)Lightness override, chroma and hue preserved
@include apply-greyscale()Swaps the live scale for the grey one on this element and its subtree
@include lightSchemeVars()Emits the full light-mode variable block
@include darkSchemeVars()Emits the full dark-mode variable block
@use "@sparkstone/css/src/vars.scss" as *;

.alert {
  --color: var(--error-color);

  background-color: cur-color(var(--surface-lc-2));
  color: cur-color(var(--text-lc-1));
  border: var(--border-width) solid get-border-color();
  border-radius: var(--border-radius);
  padding: var(--padding);
}

That is the pattern for your own components: rebind --color if you want a different hue, then pull surfaces and text from the same stops the framework uses. You inherit the contrast guarantees for free.

Icons

Three icons ship as base64 SVG data URLs, redefined per scheme so the stroke flips: --icon-chevron for select, --icon-calendar for date, --icon-clock for time. Override any of them with your own data URL.

select {
  --icon-chevron: url("data:image/svg+xml;base64,…");
}