Site theme
Flair lives in these dials and nowhere else. A site copies
@ambiqai/helia-ui/site-theme.css to src/styles/site-theme.css, loads it
from customCss immediately after the package sheets, and sets the values
below; everything else it might want to look different about is a package
change, not a site one. A site that needs a rule outside this contract opens
a component request against
AmbiqAI/helia-ui, so that
every consumer gets the same answer rather than ten sites each solving it in
their own stylesheet.
The dials
Section titled “The dials”Read out of site-theme.css itself: the declaration is the default and the
comment above it is the description, so a dial that moves without this page
moving fails validate.
| Dial | What it changes | Accepts | Default | Read by |
|---|---|---|---|---|
--helia-product-accent | The site’s accent. One value, not a vocabulary: a site carrying several product accents sets this per subtree rather than adding tokens. | Any color, normally one of the --helia-accent-* hues rather than a fresh one, so the palette stays the palette. | var(--helia-accent-slate) | AsciiTerminal, for the prompt color. No card recipe draws an accent edge, so elsewhere a site passes this into Media as --accent. |
--helia-accent-secondary | The second accent, for a treatment that needs two: a gradient, a tone, a hero band. Leave it equal to the first and nothing pairs. | Any color, on the same terms as the first accent. | var(--helia-accent-slate) | Badge tone secondary and LinkCard’s directional cue under the pointer. EditorialBand pairs it with the first accent when the hero treatment is gradient. Charts do not read it: the series ramp is --helia-chart-*, which is a palette rather than a dial. |
--helia-radius-scale | Multiplies the whole radius scale at once. | A unitless number. 0 is a square site, 1 is the package shape, above about 1.6 the pill and the card stop being distinct. | 1 | --helia-radius-xs, -sm, -md and -lg in semantic.css, and --radius in shadcn.css, so every helia-surface, every part that rounds its own chrome, and the React surfaces beside them. --helia-radius-pill is deliberately outside the scale. |
--helia-motion-scale | Multiplies every motion duration. Reduced motion zeroes it regardless, so this is the site’s dial, not the visitor’s. | A unitless number. 0 removes motion for everyone on the site; 1.5 is the slowest worth asking for. | 1 | --helia-motion-fast, -base and -slow, so helia-motion-lift, helia-motion-reveal, helia-motion-cue, helia-hover-surface, helia-button and the duration-* utilities the React parts write. Reveal and ShowcaseCarousel also read the number itself and skip their scripts when it is 0. |
--helia-font-sans | Interface and editorial type. | A full font stack, not a family name on its own. | var(--__sl-font, var(--helia-sl-font-system)) | The page frame through Starlight, and CodeBlock for its chrome. |
--helia-font-mono | Code, filenames, versions, transcripts. | A full font stack, not a family name on its own. | var(--__sl-font-mono, var(--helia-sl-font-system-mono)) | CodeBlock and AsciiTerminal. |
--helia-surface-tint--helia-surface-tint-amount | A hue mixed into every surface, and how much of it. The default mixes nothing, which is why the amount rather than the hue is what is zeroed. | Any color, and a percentage. Keep the amount in single figures: this is a cast over the page, not a background color. | transparent0% | --helia-surface-paper, --helia-surface-card and --helia-surface-card-muted, so helia-surface and its variants, helia-chip--accent, helia-chip--static, helia-button--ghost, and the --card, --popover, --muted and --secondary aliases the React parts read. |
--helia-hero-treatment | What the ground of a full-bleed band is made of. It is the site’s say rather than the caller’s, so a band takes its tone from the page and its treatment from the theme. | plain, gradient, or tinted. plain is the surface as the palette leaves it; tinted pulls the first accent into it; gradient runs the first accent into the second across the band. | plain | EditorialBand, for the paper tone. A site’s own hero composition reads it too, which is why the package names the value rather than leaving each site to spell the choice its own way. |
data-helia-density | The card padding step, and nothing else. Prose rhythm and the page frame belong to Starlight. | compact, comfortable, or the attribute left off. | unset | --helia-card-pad, so helia-surface--pad-card. |
Every default is the package default restated, so a copy with nothing changed renders exactly what the package renders, and a site owns its flair as a diff against one file.
Where a dial may be set
Section titled “Where a dial may be set”:root is the ordinary answer, and a site that sets them there needs nothing
else. A section that wants its own treatment sets them on a wrapper instead,
and the wrapper has to carry data-helia-theme:
<div data-helia-theme="warm" style="--helia-radius-scale: 1.6"> <!-- every surface under here is rounder --></div>The attribute is a hook, not a value: the package never reads what it is set
to. .helia-theme-scope does the same job where the markup cannot take an
attribute. Either one is needed because a dial is not a token but the input
to several. CSS substitutes a custom property at the element that declares
it, so --helia-radius-md composed once on :root is a finished length by
the time it inherits, and a scale set further down the tree would move
nothing. semantic.css repeats those compositions on the two hooks, which is
what lets a dial mean the same thing wherever it is set. It does not repeat
them on *: that would charge every element on every page for a feature most
pages never use.
Density is the exception and needs no hook. It is an attribute the package selects on rather than a value it substitutes, so it has always worked on any wrapper.
Two sites, one system
Section titled “Two sites, one system”The same three cards and the same section header, under the hub’s theme and
under a warmer one. Nothing below is a component variant: the difference is
the values in a site-theme.css, set here on a wrapper so both can sit on one
page.
The hub
Start building
Three cards, one section header, and nothing else changed.
Guide
Install the toolchain
Tutorial
Run the first example
Reference
Read the API
--helia-product-accent: var(--helia-accent-slate)--helia-accent-secondary: var(--helia-accent-slate)--helia-radius-scale: 1--helia-surface-tint-amount: 0%--helia-hero-treatment: plaindensity: unset
A warmer site
Start building
Three cards, one section header, and nothing else changed.
Guide
Install the toolchain
Tutorial
Run the first example
Reference
Read the API
--helia-product-accent: var(--helia-accent-orange)--helia-accent-secondary: var(--helia-accent-pink)--helia-radius-scale: 1.6--helia-surface-tint: var(--helia-accent-orange)--helia-surface-tint-amount: 7%--helia-hero-treatment: gradientdata-helia-density="comfortable"
The second theme lives in this site as a scoped example rather than in the
package. A second theme is a thing a site has. Each panel is a
site-theme.css with its selector changed from :root to a class, which is
the only edit between these and the file a site would ship, and each carries
data-helia-theme so the compositions re-derive inside it.