Theming
Every color, corner, easing curve and shadow in Noph UI comes from a CSS custom property. A theme is a set of these variables. Change one and every component that reads it updates, at build time or while the page is running.
How the theme works
A theme declares 54 color roles on :root. Each one holds both schemes in a light-dark() pair. color-scheme: light dark lets the browser pick either.
:root {
color-scheme: light dark;
--np-color-primary: light-dark(#00668c, #75ceff);
--np-color-on-primary: light-dark(#f4f9ff, #00435d);
--np-color-primary-container: light-dark(#75ceff, #4eaad9);
--np-color-on-primary-container: light-dark(#00435d, #002637);
}The same file also holds the shape, motion and elevation tokens listed below. It is plain CSS, so you can ship it as a stylesheet, inline it, or override single values from a component.
Using the default theme
Import the default theme once, in your root layout.
import 'noph-ui/defaultTheme'Then take the page background and text color from the theme.
body {
background-color: var(--np-color-background);
color: var(--np-color-on-background);
}The default theme is the source color #5fb9e9 run through the Content variant on the 2025
spec with no contrast adjustment. The generator below starts with these settings, so it matches the
shipped file token for token.
Generate your own theme
Pick a source color and Noph UI derives all 54 roles for both schemes. Every change applies to this page right away, so you can check a palette on real components. Copy CSS copies a complete replacement for the default theme to your clipboard. Reset brings back the shipped theme.
Each swatch shows the value for the color scheme you are viewing. The two hex values below a token are its light and its dark value.
Spec versions
SPEC_2021 is the original Material 3 color system. SPEC_2025 is the Material
3 Expressive revision, with brighter containers and different surface tones. The default theme uses
it.
Light and dark color schemes
Every token is a light-dark() pair, so the browser follows the operating system setting
without JavaScript or class toggling.
To force one scheme, set data-theme on the html element to light or dark.
<html lang="en" data-theme="dark">To let visitors choose, store the choice and set the attribute in an inline <script> in the <head> of your app.html. It
runs before the page paints, so the wrong scheme does not flash.
const stored = localStorage.getItem('theme')
if (stored === 'light' || stored === 'dark') {
document.documentElement.setAttribute('data-theme', stored)
}Applying a theme at runtime
Set the custom properties on the root element instead of swapping stylesheets. Only the changed values repaint. The generator above does the same.
const setTheme = (tokens) => {
for (const [token, value] of Object.entries(tokens)) {
document.documentElement.style.setProperty(token, value)
}
}
setTheme({ '--np-color-primary': 'light-dark(#00668c, #75ceff)' })Call removeProperty with the same token names to fall back to the stylesheet again.
Shape tokens
Every component uses these corner radii.
| Token | Value |
|---|---|
--np-shape-corner-none | 0 |
--np-shape-corner-extra-small | 0.25rem |
--np-shape-corner-small | 0.5rem |
--np-shape-corner-medium | 0.75rem |
--np-shape-corner-large | 1rem |
--np-shape-corner-extra-large | 1.75rem |
--np-shape-corner-full | 9999px |
Motion tokens
Motion tokens pair a duration with an easing curve. Spatial tokens move things. Effects tokens change color and opacity. The expressive curves overshoot slightly and settle. The standard curves do not.
| Token | Duration |
|---|---|
--np-motion-expressive-fast-spatial | 350ms |
--np-motion-expressive-default-spatial | 500ms |
--np-motion-expressive-slow-spatial | 650ms |
--np-motion-expressive-fast-effects | 150ms |
--np-motion-expressive-default-effects | 200ms |
--np-motion-expressive-slow-effects | 300ms |
--np-motion-standard-fast-spatial | 350ms |
--np-motion-standard-default-spatial | 500ms |
--np-motion-standard-slow-spatial | 750ms |
--np-motion-standard-fast-effects | 150ms |
--np-motion-standard-default-effects | 200ms |
--np-motion-standard-slow-effects | 300ms |
transition: background-color var(--np-motion-expressive-default-effects);Elevation tokens
Four layered shadows, from lightest to strongest. Cards, menus, dialogs and hovered FABs use them, and you can use them too.
box-shadow: var(--np-elevation-1);
box-shadow: var(--np-elevation-2);
box-shadow: var(--np-elevation-3);
box-shadow: var(--np-elevation-4);Overriding a single component
Components expose their own properties, named --np-<component>-<part>-<role>. Set one as a style prop to restyle
a single instance, or on any ancestor to restyle a whole region.
<script lang="ts">
import { Button } from 'noph-ui'
</script>
<Button variant="filled">Default</Button>
<Button
variant="filled"
--np-filled-button-container-color="var(--np-color-tertiary)"
--np-filled-button-label-text-color="var(--np-color-on-tertiary)"
>
Tertiary
</Button>
<Button variant="filled" shape="square" --np-button-shape="var(--np-shape-corner-extra-small)">
Square
</Button>