Appearance
Appearance is one object that sets your app's whole theme: colors, density, corners, shadows, borders and font preferences.
@nxds/core turns that object into the theme's CSS. The examples use the
React provider or the Vue plugin you copied in
Install.
Fields
Color is one setting out of many. Density, corners, shadows, border width, type and motion are settings too, so "make this product denser" is a one-field change, not a refactor.
Every option ships in the stylesheet. The provider applies colors as CSS
variables, mode as the .dark class, and density, corners, shadows and
border width as data attributes. Switching an option needs no rebuild.
| Field | Values | Sets |
|---|---|---|
mode | 'light', 'dark', 'system' | Light or dark. system follows the operating system. |
brandColor | Any CSS color. Default #0a0a0a. | The primary color. |
surfaceTone | 'stone', 'neutral', 'zinc', 'slate', 'gray'. Default 'zinc'. | The gray used for backgrounds. |
lightContrast / darkContrast | 0 to 100. Default 50. | How strong text and borders are, separately for each mode. |
density | 'tight', 'compact', 'default', 'comfortable', 'relaxed', 'spacious'. Default 'default'. | Spacing. |
corners | 'square', 'subtle', 'smooth', 'round', 'extra-round'. Default 'subtle'. | Corner radius. |
elevation | 'flat', 'quiet', 'soft', 'standard', 'strong'. Default 'quiet'. | Shadows. |
stroke | 'fine', 'normal', 'strong'. Default 'normal'. | Border width. |
prefs | NxdsAppearancePrefs | Fonts, font sizes, reduced motion, pointer cursors, font smoothing. |
Fonts and font sizes apply to nxds components only. Reduced motion, pointer cursors and font smoothing apply to the whole page. All three are off until a user turns them on.
Change the theme at runtime
Read and update the appearance with useNxdsAppearance:
Read and update the appearance with useNxdsAppearance:
state and resolvedMode are refs, so the template updates when the theme
changes.
Use useNxdsAppearance to build theme controls for your own settings page.
To switch brands, set brandColor the same way. nxds builds the new palette
and restyles every component at once, without a rebuild.
Brand color
brandColor sets the primary color. nxds builds the full primary palette from
it, including hover and pressed shades.
In light mode, the button uses your color, with very light colors darkened a little. In dark mode, dark brand colors get lighter so the button stays visible. A black brand becomes a white button in dark mode.
To let users pick a brand color, copy the appearance/brand-color-field
component:
The field offers nine presets (Default, Indigo, Blue, Violet, Rose, Orange, Amber, Green and Teal), a color picker and a hex input. A color that matches no preset shows as Custom.
To build your own picker, @nxds/core exports the presets:
| Export | Returns |
|---|---|
BRAND_COLOR_PRESETS | The preset list |
BASE_TONE_OPTIONS | The surface tone list |
findBrandColorPreset(brandColor) | The matching preset, or undefined for a custom color |
findBrandColorPreset matches any opaque CSS color, such as #4F46E5,
4f46e5 or rgb(79 70 229). A translucent color returns undefined.
Surface tone
surfaceTone sets the gray behind your content. Every tone is close to
neutral:
| Tone | Looks |
|---|---|
neutral | Pure gray |
stone | Slightly warm |
gray | Slightly cool |
zinc | Cool, slightly violet |
slate | Coolest |
In light mode, pages, cards and popovers stay white for every tone. The tone shows in hover and pressed states, muted surfaces and dark mode.
Contrast
lightContrast and darkContrast set how strong text, borders and hover
states are. Each runs from 0 to 100, with 50 as the default.
- Raise contrast for stronger text and clearer borders.
- Lower it for a softer look. Text stays readable at
0. - Brand and status colors keep their color at every value.
Each mode keeps its own value, so a user can raise contrast in dark mode only. For the contrast targets behind each value, see Color.
Fonts
prefs.uiFont, prefs.headingFont and prefs.codeFont set the
font-family stacks for UI text, headings and code.
headingFont is null by default, so headings use the UI font.
Load a font before you name it. Install its Fontsource package, then import it in your nxds CSS entry:
init --font does both
steps, and sets the font in your theme. FONT_OPTIONS in @nxds/core lists the fonts it offers,
with each font's stack and package:
| Category | Fonts |
|---|---|
| Sans | Geist, Inter, Figtree, DM Sans, Manrope, IBM Plex Sans |
| Serif | Lora, Playfair Display |
| Mono | Geist Mono, JetBrains Mono |
Icons
nxds components draw their own icons, such as a Select's chevron, from
lib/icons.ts. That file draws Phosphor. To use another library, pick it when
you set up:
| Library | --icons |
|---|---|
| Phosphor | phosphor |
| Lucide | lucide |
| Tabler Icons | tabler |
| HugeIcons | hugeicons |
| Remix Icon | remix |
Remix Icon uses the Remix Icon License, not MIT. It bars logo use and building a competing icon set.
Icons you pass to a component, such as a Button's icon, come from any library you like.
Shuffle
shuffleNxdsAppearance returns a random theme built from the curated options.
It picks the brand preset, surface tone, density, corners, elevation, stroke,
UI font and heading font.
Shuffle keeps mode, contrast and the other preferences. Every brand preset passes the contrast checks on every surface tone.
Shuffle picks from every font in FONT_OPTIONS. If your app loads only some
of them, pass those as fonts:
To try it, select Shuffle in the theme panel on any docs page, or in the
Playground. Then select Use this theme to copy the init
command that sets it up.
Save the theme
The provider saves the appearance and restores it before the page paints. Install shows the setup for Next.js, and Set up Vite for Vite.
To change the theme inside one element, see Overrides.