Neutral tokens
Surface, text, and border tokens shared by every brand. Each one is
a named alias onto a specific step of the
| Token | Swatch | Maps to |
|---|---|---|
--eun-surface-color |
--eun-color-grey-0 |
|
--eun-border-color / --eun-divider-color |
--eun-color-grey-300, text mixed 12% into the surface in dark mode |
|
--eun-disabled-color |
--eun-color-grey-200 |
|
--eun-content-hover |
translucent overlay | |
--eun-content-pressed |
translucent overlay | |
--eun-text-color |
--eun-color-grey-800 |
|
--eun-text-secondary |
--eun-color-grey-600 (light), #d2d5de (dark) |
|
--eun-readonly-color / --eun-text-readonly-color |
--eun-color-grey-600 / --eun-color-grey-400 |
|
--eun-placeholder-color / --eun-text-disabled-color / --eun-disabled-content |
--eun-color-grey-400 / --eun-color-grey-500 / --eun-color-grey-400 |
Usage
These are the tokens to reach for in almost all app-level CSS: they carry semantic meaning ("this is the surface color") and stay correct across every brand and mode, unlike reaching for a raw grey step directly.
.custom-card {
background: var(--eun-surface-color);
color: var(--eun-text-color);
border: 1px solid var(--eun-border-color);
}
.custom-card[aria-disabled="true"] {
background: var(--eun-disabled-color);
color: var(--eun-text-disabled-color);
}
This is exactly the pattern eun-button's disabled state and
eun-tab-group's divider use internally. See
These values only apply once a theme stylesheet is loaded: see
Brand-tinted variants
The tokens above are deliberately brand-independent: --eun-surface-color
is the same white (light) / grey-0 (dark) no matter which prebuilt theme
is active, so a component built against it never clashes with any brand.
Each one also has a -tint counterpart that mixes a small amount
(5%) of the active theme's primary-500 into that same base value.
These stay opt-in and are not used by any component internally, so
reach for them only when you deliberately want a surface, divider, or
hover wash that reads as "branded" rather than neutral.
| Token | Swatch | Derived from |
|---|---|---|
--eun-surface-tint |
--eun-surface-color + 5% primary |
|
--eun-border-tint / --eun-divider-tint |
--eun-border-color + 5% primary |
|
--eun-content-hover-tint |
--eun-content-hover + 5% primary |
|
--eun-content-pressed-tint |
--eun-content-pressed + 5% primary |
.branded-card {
background: var(--eun-surface-tint);
border: 1px solid var(--eun-border-tint);
}
.branded-card:hover {
background: var(--eun-content-hover-tint);
}
Text and disabled/readonly tokens intentionally don't get a tint variant: those carry a legibility or muted-state contract that a brand wash would work against.
Translucent variants
For overlays, frosted headers, or muted text that must let the content
underneath show through, the theme also exposes the surface and text colors
at fixed opacities. They are computed with color-mix() from
--eun-surface-color and --eun-text-color, so they follow the active brand
and mode without any extra work.
| Token | Derived from |
|---|---|
--eun-surface-transparent-25 / -50 / -75 |
--eun-surface-color at 25%, 50% and 75% |
--eun-text-transparent-25 / -50 / -75 |
--eun-text-color at 25%, 50% and 75% |
--eun-text-on-dark-secondary |
white at 70%, the same in light and dark |
--eun-text-on-dark-secondary is the one exception to the "follows the theme"
rule. Use it for muted text on a surface that stays dark whatever the active
mode, such as a footer.
.sticky-header {
background: var(--eun-surface-transparent-75);
backdrop-filter: blur(8px);
}
.dark-footer {
color: var(--eun-text-on-dark-secondary);
}
For an elevated surface in dark mode, reach for --eun-surface-tint above.
Inverse surface
Some elements stay dark whatever the active mode: a dark toolbar, a footer, a highlighted section. Their background is a role rather than a brand color, so it gets its own tokens instead of a third color scale.
| Token | Swatch | Value |
|---|---|---|
--eun-surface-inverse |
#0b1620 in both modes |
|
--eun-text-on-inverse |
#f5f7fa in both modes |
|
--eun-text-on-inverse-secondary |
--eun-text-on-inverse at 70% |
|
--eun-border-on-inverse |
--eun-text-on-inverse at 20% |
|
--eun-content-hover-on-inverse |
--eun-text-on-inverse at 8% |
|
--eun-content-pressed-on-inverse |
--eun-text-on-inverse at 14% |
|
--eun-backdrop-color |
--eun-surface-inverse at 45% |
eun-toolbar and eun-link use them by default with theme="dark". A
custom theme sets the surface with the inverseSurfaceColor option of
--eun-text-on-inverse. The last five are
computed with color-mix() and follow it. --eun-backdrop-color is the
dimmed layer behind eun-modal and eun-drawer.
.site-footer {
background: var(--eun-surface-inverse);
color: var(--eun-text-on-inverse);
border-top: 1px solid var(--eun-border-on-inverse);
}
.site-footer small {
color: var(--eun-text-on-inverse-secondary);
}
Sunken surfaces
Tracks, wells and filled containers need to read one step below whatever
they sit on: the toggle group's track, the switch and slider tracks, an
unchecked checkbox, a filled card, a collapse, a flat button. A fixed grey
step can't do that: a grey-100 surface disappears into a grey-100
page. The sunken tokens are a translucent wash of --eun-text-color
instead, so they darken a light background and lighten a dark one, and
stay visible on any container. Nested sunken surfaces stack, so a track
inside a collapse still reads one step darker than the collapse.
| Token | Swatch | Light | Dark |
|---|---|---|---|
--eun-surface-sunken |
--eun-text-color at 5% |
--eun-text-color at 8% |
|
--eun-surface-sunken-hover |
--eun-text-color at 9% |
--eun-text-color at 12% |
|
--eun-surface-sunken-pressed |
--eun-text-color at 12% |
--eun-text-color at 16% |
|
--eun-surface-sunken-strong |
--eun-text-color at 8% |
--eun-text-color at 16% |
Dark mode uses a stronger mix because a light wash over a dark surface reads fainter than a dark wash over a white one.
--eun-surface-sunken is meant for bordered controls and for containers. A borderless track (the slider, progress bar, password
strength and loader bars) has nothing but its fill to show where it is, so
it uses --eun-surface-sunken-strong instead.
.custom-track {
background: var(--eun-surface-sunken);
}
.custom-track:hover {
background: var(--eun-surface-sunken-hover);
}
Reach for --eun-surface-color or another opaque color for anything that
floats or must hide what is behind it (a popover, a toast, a tooltip, a
sticky header), and for the sunken tokens for a
control that sits inside a surface. Like the translucent variants, they are
resolved where the theme is declared: a section that redefines
--eun-text-color on its own does not update them.