Customization
Variant Inheritance
How components inherit colors from ancestor layouts via CSS custom-property cascade.
VaneUI has two color behaviors. Components with their own appearance (Button, Card, Badge, and most others) emit data-appearance/data-variant and paint their own colors regardless of context. Inherit-mode components (the typography components, plus Icon) emit nothing and read their colors from the nearest ancestor through native CSS custom-property cascade. This page explains the split, how the cascade works, and how to control it.
How variants and appearances cascade through nesting
VaneUI's color system has three layers:
:root → --text-color: dark, --bg-color: white <Card filled primary> → --text-color: white, --bg-color: dark (emits attrs, own rule fires) <Button> → --text-color: dark, --bg-color: white (emits its own primary-outline attrs) <Text> → inherits white text from Card (inherit mode, no attrs) <Mark> → --text-color: amber (warning) (emits its own warning attrs)Which components set vs. inherit
Components that paint their own colors
Every component with a concrete appearance default emits data-appearance and data-variant, so its own CSS rule fires and it keeps its colors regardless of the surrounding context:
Button, Card, Badge, Chip, Code, Kbd, Mark, Input, Checkbox, IconButton, NavLink (and any component you give an explicit appearance).
A default <Button> inside a <Card filled primary> renders in its own primary-outline palette (dark text on white), not the Card's white-on-dark. A <Mark> inside a <Card filled danger> stays warning-yellow, and a <Badge> keeps its secondary colors. These components do not inherit color; set an explicit appearance to change them.
Components that inherit
Two kinds of components emit no data-appearance, so they read colors from the nearest ancestor that set them:
Explicit props always win
When you explicitly set an appearance or variant on a component, VaneUI emits data attributes and the component's own CSS rule fires, overriding any inherited values:
I'm white
I'm amber
I'm green
<Card filled primary> {/* Inherits from Card: white text on dark background */} <Text>I'm white</Text>
{/* Explicit appearance: own CSS rule fires, ignoring the Card */} <Text warning>I'm amber</Text> <Text success>I'm green</Text></Card>An explicit appearance opts out of inheritance completely, so pick one that reads on the surface you are on. <Text primary outline> inside a filled primary Card resolves to the surface's own dark colour and disappears into it.
Nested layouts
When multiple layout components are nested, each child inherits from its nearest ancestor that sets variables, not from the outermost ancestor:
I'm dark (inherits from Stack, not Card)
I'm white on red (inherits from danger Stack)
<Card filled primary> {/* Card sets: --text-color = white, --bg-color = dark */}
<Stack outline primary> {/* Stack sets its own: --text-color = dark, --bg-color = white */} {/* (explicit outline primary → own CSS rule fires) */}
<Text>I'm dark (inherits from Stack, not Card)</Text> </Stack>
<Stack filled danger> {/* Stack sets its own: --text-color = white, --bg-color = red */}
<Text>I'm white on red (inherits from danger Stack)</Text> </Stack></Card>ThemeProvider overrides
ThemeProvider.themeDefaults sets a component's default appearance for a subtree. A concrete appearance emits data attributes and pins the color; giving an inherit-mode component a concrete appearance makes it stop inheriting:
{/* All Badges in this subtree default to danger */}<ThemeProvider themeDefaults={{ badge: { danger: true } }}> <Card filled primary> {/* This Badge renders red (danger), unaffected by the Card */} <Badge>Alert</Badge> </Card></ThemeProvider>The emission rule
The data-attribute gate uses one rule:
Emit data-appearance and data-variant for any concrete (non-inheritAppearance) appearance. They are suppressed only in inherit mode (appearance="inheritAppearance", which activates inheritColor) or when a component has no appearance at all (Icon).
This means:
Granular inheritance props
By default, the inheritAppearance appearance keyword inherits everything (color, size, background, and border) from the nearest ancestor. But sometimes you need selective inheritance: a Link inside a Title should inherit font-size (so the link matches the heading size) but keep its own link-blue color.
VaneUI provides four independent boolean toggle props for this:
| Prop | What it inherits | Negative toggle |
|---|---|---|
inheritSize | Font-size and line-height from parent | noInheritSize |
inheritColor | Text color via CSS variable cascade | noInheritColor |
inheritBg | Background color via CSS variable cascade | noInheritBg |
inheritBorder | Border color via CSS variable cascade | noInheritBorder |
How inheritAppearance expands
When a component has inheritAppearance appearance (the default for Text, Title, SectionTitle, PageTitle, Blockquote, Label, List, ListItem, and Divider), VaneUI expands it into color, background, and border inheritance, but not size:
<Text inheritAppearance> ↓ expands to: inheritColor + inheritBg + inheritBorder (NOT inheritSize: size uses own --fs variable so <Text sm> works as expected)Size inheritance is separate. Only Link and Mark have inheritSize: true in their defaults (Code and Kbd achieve the same effect via em-relative geometry, described below). You can also set it explicitly:
Inherited color, own size
Inherited color AND size
<Card filled primary> {/* Inherits color (white) but uses own md size */} <Text inheritAppearance>Inherited color, own size</Text>
{/* Explicit inheritSize: also inherits font-size from parent */} <Text inheritAppearance inheritSize>Inherited color AND size</Text></Card>Link, Mark: exact size inheritance via inheritSize
Link and Mark are not inheritAppearance-default components (Link sets no appearance and renders its own link-blue, Mark defaults to warning), so the inheritAppearance expansion does NOT fire. Instead, they have inheritSize: true set explicitly in their defaults. They render at the exact font-size of the nearest typography ancestor.
Check the documentation for details
<Title lg> Check the <Link href="/docs">documentation</Link> for details</Title>Code, Kbd: em-relative geometry
Code and Kbd use a different mechanism: their .vane-code / .vane-kbd rules override --spacing to 0.25em locally, so the entire geometry pipeline (font-size, padding, border-radius, gap) resolves in em, proportional to the parent's font-size.
Run npm install to add the package
<Title lg> Run <Code>npm install</Code> to add the package</Title>So inline Code feels right-sized in every context: body text at 14px / 16px, headings at 21px / 24px, hero displays at 42px / 48px.
Opting out (Link, Mark)
Use noInheritSize to keep Link or Mark at its own size instead of the parent's:
Heading with fixed-size link
<Title lg> Heading with <Link noInheritSize href="/docs">fixed-size link</Link></Title>The Link renders at its default md size while the Title is lg. For Code/Kbd, the size prop already controls the ratio: pass any of xs / sm / md / lg / xl to adjust.
Responsive overrides inheritSize
Title, PageTitle, and SectionTitle have responsiveSizing: true in their defaults. Responsive sizing takes priority over inheritSize: a responsive heading always uses its viewport-scaled size, even if inheritSize is set via the inheritAppearance expansion.