# VaneUI Documentation > Complete documentation for the VaneUI React component library (`@vaneui/ui` 1.5.0), > every doc page concatenated into one file. Each page starts with `# `, its URL and > one import line for the `@vaneui/ui` exports its examples use. Component pages end with > their props, grouped by category with defaults marked, generated from the installed package. - Repository: https://github.com/vaneui/vaneui - Documentation: https://vaneui.com - Package: `@vaneui/ui` 1.5.0 - Index: https://vaneui.com/llms.txt - Contact: hello@vaneui.com Pages follow the site navigation: Getting Started, Basic Components, Form Components, Typography Components, Layout Components, Overlay Components, Customization, Reference. Each page is also available on its own as Markdown at its URL plus `.md`. # Installation > Install @vaneui/ui with npm, yarn or pnpm, add its CSS through Tailwind CSS v4 or the prebuilt bundle, and wrap your React app in ThemeProvider. Source: https://vaneui.com/docs/getting-started/installation Import: `import { Button, Card, Stack, ThemeProvider, useTheme } from "@vaneui/ui"` Install VaneUI, a React component library built with TypeScript and Tailwind CSS, using your preferred package manager: ```bash npm install @vaneui/ui ``` ```bash yarn add @vaneui/ui ``` ```bash pnpm add @vaneui/ui ``` ## CSS setup VaneUI supports two CSS setup options depending on your project configuration. ### Option 1: with Tailwind CSS (recommended) If your project uses Tailwind CSS v4, VaneUI plugs in directly. Tailwind scans VaneUI components and generates only the CSS classes you use. Install Tailwind CSS if you haven't already: ```bash npm install -D tailwindcss @tailwindcss/postcss ``` Add the following to your main CSS file: ```css @import "tailwindcss"; @import "@vaneui/ui/tokens"; @import "@vaneui/ui/vars"; @source "../node_modules/@vaneui/ui"; ``` This setup: - Imports Tailwind CSS base styles - Imports VaneUI theme tokens (registers custom breakpoints and design tokens with Tailwind) - Imports VaneUI CSS variables and component rules - Tells Tailwind to scan VaneUI components for class generation > The `@vaneui/ui/tokens` import is required for responsive props like `mobileStack`, `tabletHide`, and responsive typography to work. It registers VaneUI's custom breakpoints (`mobile`, `tablet`, `desktop`) with your project's Tailwind CSS instance. ### Option 2: without Tailwind CSS If your project doesn't use Tailwind CSS, you can import VaneUI's pre-built CSS bundle which includes all component styles. Add the following to your main CSS file: ```css @import "@vaneui/ui/css"; ``` This setup: - Imports the complete pre-built VaneUI stylesheet (component styles and CSS variables in one file) - Works without any Tailwind configuration The pre-built stylesheet is compiled for VaneUI's own components; it does not contain Tailwind's full utility set. A Tailwind class passed through `className` or `extraClasses` has no effect unless it happens to be in the bundle, and most (`max-w-md`, `p-6`, `size-8`, `ms-auto`, `z-40`) are not. For arbitrary utilities, use Option 1 or the `style` prop. ## ThemeProvider setup Wrap your application in `ThemeProvider`. Without it, each component falls back to its own built-in default theme, but `themeDefaults`, `extraClasses`, and `themeOverride` only apply inside a provider. Place it once at the root, above every VaneUI component. ```tsx import { ThemeProvider } from '@vaneui/ui'; function Root() { return ( ); } ``` `ThemeProvider` needs no props to apply the built-in defaults. To set application-wide defaults, extra classes, or theme overrides, see [Using ThemeProvider](/docs/customization/using-theme-provider). For a Next.js App Router project, see [Next.js](/docs/getting-started/nextjs); to connect an AI coding assistant through the MCP server, llms.txt or the Markdown copy of each page, see [AI Assistants](/docs/getting-started/ai-assistants). ## Usage Start using VaneUI components in your React application: ```tsx import { Button, Stack } from '@vaneui/ui'; function App() { return ( ); } ``` ## Per-component imports For better tree-shaking, you can import components directly: ```tsx import { Button } from '@vaneui/ui/button'; import { Card } from '@vaneui/ui/card'; import { useTheme } from '@vaneui/ui/theme'; ``` This is optional. Importing from `@vaneui/ui` works the same way. Per-component imports can reduce bundle size when using only a few components with bundlers that don't fully tree-shake barrel exports. Available subpath imports include: `@vaneui/ui/button`, `@vaneui/ui/iconButton`, `@vaneui/ui/icon`, `@vaneui/ui/badge`, `@vaneui/ui/card`, `@vaneui/ui/checkbox`, `@vaneui/ui/chip`, `@vaneui/ui/code`, `@vaneui/ui/divider`, `@vaneui/ui/img`, `@vaneui/ui/input`, `@vaneui/ui/kbd`, `@vaneui/ui/label`, `@vaneui/ui/mark`, `@vaneui/ui/menu`, `@vaneui/ui/modal`, `@vaneui/ui/navLink`, `@vaneui/ui/overlay`, `@vaneui/ui/popup`, `@vaneui/ui/section`, `@vaneui/ui/container`, `@vaneui/ui/row`, `@vaneui/ui/col`, `@vaneui/ui/stack`, `@vaneui/ui/grid`, `@vaneui/ui/typography`, `@vaneui/ui/layout`, `@vaneui/ui/theme`, `@vaneui/ui/props`. # Core Concepts > How VaneUI's boolean props API works in React: one prop per category, how props become Tailwind CSS classes and CSS variables, and where custom classes fit. Source: https://vaneui.com/docs/getting-started/core-concepts Import: `import { Button, Card, Stack, ThemeProvider } from "@vaneui/ui"` VaneUI turns common design decisions into boolean props. Instead of memorizing property names and values, you compose intent: `primary`, `lg`, `outline`, `rounded`. This page covers the boolean props API, the three-layer component architecture, prop extraction priority, and the three-tier CSS variable system. ## Boolean props API At its core, VaneUI uses **boolean props** instead of string enums for cleaner JSX: ```tsx // VaneUI approach // Instead of traditional ``` Props are organized into **categories**: - **size**: `xs`, `sm`, `md`, `lg`, `xl` - **appearance**: `primary`, `accent`, `secondary`, `tertiary`, `success`, `danger`, `warning`, `info`, `inheritAppearance` - **variant**: `filled`, `outline`, `ghost` (`ghost`: transparent background, no border, appearance-colored text, tinted hover background) - **shape**: `rounded`, `pill`, `sharp` - **typography**: `fontSans`, `fontSerif`, `fontMono`, `fontSemibold`, `fontBold`, etc. - **layout**: `flex`, `column`, `itemsCenter`, `justifyBetween`, etc. - **inheritance**: `inheritSize`, `inheritColor`, `inheritBg`, `inheritBorder` (and `noInherit*` toggles) Text, Title, SectionTitle, PageTitle, Blockquote, Label, List, Divider, and Spinner default to `inheritAppearance`, which means they [inherit colors from their parent element](/docs/customization/variant-inheritance) via CSS variable cascade rather than setting their own color. Link and Mark default to `inheritSize`, so they inherit font-size from their parent while keeping their own appearance color; Code and Kbd achieve the same effect through em-relative geometry. ## Three-layer component architecture VaneUI components follow a three-layer architecture: ### Layer 1: React component Each component gets its theme from `ThemeContext` via `useTheme()` and passes props to `ThemedComponent`, which computes the final CSS classes. ### Layer 2: theme system The `ComponentTheme` class orchestrates class generation. It walks a tree of `BaseClassMapper` subclasses (like `FontSizeClassMapper`, `RadiusClassMapper`) that each generate specific CSS classes based on the active props. ### Layer 3: CSS variables Components output **data attributes** (`data-size`, `data-appearance`, `data-variant`) that CSS rules in `rules.css` use to set CSS variables. These variables are then consumed by Tailwind utility classes. ## Prop extraction When you pass props, VaneUI selects ONE value per category based on priority: ```tsx // Button defaults: { sm: true, primary: true, outline: true, ... } // User props: { lg: true, filled: true } // Result: size='lg' (prop overrides default sm), // appearance='primary' (default kept), // variant='filled' (prop overrides default outline) ``` **Priority order**: 1. Props explicitly set to `true` 2. Defaults set to `true` (unless explicitly set to `false` in props) ## Prop combinations Boolean props can be combined naturally to create the exact styling you need: ```tsx Filled secondary card Stack with centered items (column, gap, padding, and flexWrap are defaults) ``` ## CSS variable system VaneUI uses a [three-tier CSS variable system](/docs/customization/css-variables): ### Tier 1: unit variables Set by component class based on `data-size`: ```css .vane-button[data-size="md"] { --fs-unit: 8; /* Font size unit */ --py-unit: 2; /* Padding Y unit */ } ``` ### Tier 2: computed variables Calculated from unit variables using base multipliers: ```css [data-size] { --fs: calc(var(--fs-unit) * var(--fs-base)); /* --fs-base = 0.125rem */ --py: calc(var(--py-unit) * var(--spacing)); /* --spacing = 0.25rem */ --px: calc(var(--aspect-ratio) * var(--py-unit) * var(--spacing)); --br: calc(var(--br-unit) * var(--br-base)); /* --br-base = 0.25rem */ --gap: calc(var(--gap-unit) * var(--spacing)); } ``` ### Tier 3: semantic color variables Set by `data-variant` + `data-appearance` attributes: ```css [data-variant="outline"][data-appearance="primary"] { --text-color: var(--color-text-primary); --bg-color: var(--color-bg-primary); --border-color: var(--color-border-primary); } ``` ## Mixing custom classes You can always [add your own Tailwind classes](/docs/customization/customizing-styles) via `className` to fine-tune any edge case: ```tsx demo ``` User-provided `className` takes precedence over theme classes for the same properties (via `twMerge`). ## Customization options ### CSS variables Override colors globally or for any subtree: ```css :root { --color-text-primary: #8b5cf6; --color-bg-primary: #f3e8ff; --color-border-primary: #c4b5fd; } ``` ### ThemeProvider defaults [Set default prop values](/docs/customization/theme-defaults) for all components, including overlay components (Overlay, Modal, Popup): ```tsx demo ``` ### Extra classes Add additional CSS classes based on active props: ```tsx demo ``` ### Theme override function A function that receives the resolved theme and returns it modified. Unlike `themeDefaults` (which sets default props) and `extraClasses` (which adds classes keyed by active prop), it has direct access to the whole theme tree, including each component's always-on base classes: ```tsx { // Modify base classes theme.button.main.base += ' uppercase tracking-wide'; return theme; }}> ``` # Usage Basics > Core VaneUI patterns in React: boolean size and appearance props, layout components, responsive visibility, typography, icons, images and overlays. Source: https://vaneui.com/docs/getting-started/usage-basics Import: `import { Badge, Button, Card, Col, Container, Icon, Img, List, ListItem, Modal, ModalBody, ModalCloseButton, ModalFooter, ModalHeader, PageTitle, Popup, PopupTrigger, Row, Section, SectionTitle, Stack, Text, Title } from "@vaneui/ui"` VaneUI components share a boolean props API for size, appearance, variant, and layout. This page covers the core usage patterns: size and appearance props, layout components, responsive breakpoints, typography, icons, images, overlays, and when to prefer props over Tailwind classes. ## Component props VaneUI components use boolean props for styling. Most components support size, appearance, and layout props. ### Size props Components support five sizes: ```tsx import { Button, Stack } from '@vaneui/ui'; function SizeExample() { return ( ); } ``` > Most components default to `md`. Button, IconButton, NavLink, MenuItem, MenuLabel, Label, and Tooltip default to `sm`. ### Appearance props Control visual appearance with semantic props: ```tsx import { Button, Row } from '@vaneui/ui'; function AppearanceExample() { return ( ); } ``` ### Filled and outline variants All components support `filled`, `outline`, and `ghost` variants. `outline` is the default for most components. `ghost` renders a transparent background with no border and appearance-colored text, plus a tinted hover background: ```tsx import { Button, Card, Stack, Container, Row } from '@vaneui/ui'; function FilledOutlineExample() { return ( {/* Buttons */} {/* Cards */} Filled Card Outline Card (default) {/* Layout Components */} Filled Container Outline Container (default) ); } ``` ### Shape props Control border radius: ```tsx import { Button, Row } from '@vaneui/ui'; function ShapeExample() { return ( ); } ``` ## Layout components ### Stack Arrange children vertically with consistent spacing: ```tsx import { Stack, Text } from '@vaneui/ui'; function StackExample() { return ( Item 1 Item 2 Item 3 ); } ``` ### Row and Col Compose layouts: ```tsx import { Row, Col, Card } from '@vaneui/ui'; function GridExample() { return ( Column 1 Column 2 ); } ``` ### Responsive breakpoints Use breakpoint props to change layout on different screen sizes: ```tsx import { Row, Card } from '@vaneui/ui'; function ResponsiveLayout() { return ( // Row on desktop, column on tablet and below Left Right ); } ``` Available breakpoint props: - `mobileStack`: Column on mobile and below (below 48rem) - `tabletStack`: Column on tablet and below (below 64rem) - `desktopStack`: Column on desktop and below (below 80rem) ## Typography VaneUI provides typography components with consistent hierarchy: ```tsx import { PageTitle, SectionTitle, Title, Text, Stack } from '@vaneui/ui'; function TypographyExample() { return ( Page Title (h1) Section Title (h2) Title (h3) Body text (p) ); } ``` ### Automatic responsive typography Typography components (`PageTitle`, `SectionTitle`, `Title`) and layout components (`Section`) automatically scale across breakpoints: no props needed: ```tsx import { PageTitle, SectionTitle, Title, Section } from '@vaneui/ui'; function ResponsiveTypography() { return (
{/* Font size automatically reduces on smaller screens */} Welcome to My App Section Heading Content Title
); } ``` - `PageTitle` scales: e.g., 24 units on desktop, 21 on tablet, 18 on mobile - `SectionTitle` scales: e.g., 18 units on desktop, 16 on tablet, 14 on mobile - `Title` scales: e.g., 12 units on desktop, 11 on tablet, 10 on mobile - `Section` padding/gap scales automatically across breakpoints ### Typography color inheritance List items inherit appearance from their parent List component via CSS variables: ```tsx import { List, ListItem, Stack } from '@vaneui/ui'; function ListInheritance() { return ( Inherits success color Also inherits success color Inherits danger color Also inherits danger color ); } ``` ## Images Display images with VaneUI styling using the `Img` component: ```tsx import { Img, Row } from '@vaneui/ui'; function ImageExample() { return ( Photo Avatar Banner ); } ``` `Img` supports shape props (`rounded`, `pill`, `sharp`), `border`, `shadow`, size props, and object-fit props (`objectCover`, `objectContain`, `objectFill`). ## Icons Wrap any SVG with the `Icon` component to inherit the parent's font color and scale with the size system: ```tsx import { Icon, Row, Text } from '@vaneui/ui'; import { Star, AlertCircle } from 'react-feather'; function IconExample() { return ( Inline with text ); } ``` ### Icon container mode By default `Icon` renders as a bare glyph that inherits color from its parent. Adding any of `padding`, `border`, `insetRing`, `shadow`, or `filled` switches it into *container mode*: the icon gains a sized box that shape props (`rounded`, `pill`, `sharp`) can act on: ```tsx import { Icon, Row } from '@vaneui/ui'; import { Check, Heart, AlertCircle } from 'react-feather'; function IconContainerExample() { return ( ); } ``` Padding and radius scale with the size prop, so the same composition works across `xs` through `xl`. ## Overlays and modals Use `Modal` for dialogs that require user attention. Modals include focus trapping, scroll lock, and ARIA attributes by default. Modal provides sub-components for structured layout: `ModalHeader`, `ModalBody`, `ModalFooter`, and `ModalCloseButton`. ```tsx import { Modal, ModalHeader, ModalBody, ModalFooter, ModalCloseButton, Button, Text } from '@vaneui/ui'; import { useState } from 'react'; function ModalExample() { const [open, setOpen] = useState(false); return ( <> setOpen(false)}> Dialog Title Dialog content goes here. ); } ``` ### PopupTrigger Use `PopupTrigger` as a convenience wrapper that manages open/close state for `Popup` automatically. Pass the popup content via the `popup` prop and the trigger element as children: ```tsx import { PopupTrigger, Button, Card, Text } from '@vaneui/ui'; function PopupExample() { return ( Popup content}> ); } ``` `PopupTrigger` selects its trigger mode with three boolean props: `triggerOnClick` (the default when none is set), `triggerOnHover`, and `triggerOnFocus`. `Popup` positions itself relative to its anchor through a placement prop: `placeTop`, `placeTopStart`, `placeTopEnd`, `placeBottom`, `placeBottomStart`, `placeBottomEnd`, `placeLeft`, `placeLeftStart`, `placeLeftEnd`, `placeRight`, `placeRightStart`, and `placeRightEnd`. ```tsx Anchored below, aligned to the start edge ``` See the [Overlay Components](/docs/overlay-components/overlay) section for `Overlay`, `Modal`, and `Popup` documentation. ## Responsive visibility Hide components below a breakpoint, or show them only below one: ```tsx import { Text, Stack } from '@vaneui/ui'; function ResponsiveVisibility() { return ( Always visible Hidden on mobile (below 48rem) Hidden on tablet (below 64rem) Hidden on desktop (below 80rem) Shown only on mobile (below 48rem) Shown only below tablet width (64rem) ); } ``` Make a component full width below a breakpoint while it keeps its own width above it: ```tsx ``` ## Custom HTML tags Use the `tag` prop to render different HTML elements: ```tsx import { Text, Button } from '@vaneui/ui'; function TagExample() { return (
Heading rendered as h1 Text rendered as span
); } ``` ## Combining with custom classes Add custom Tailwind classes via `className`: ```tsx import { Button, Card } from '@vaneui/ui'; function CustomStyling() { return ( ); } ``` User-provided `className` takes precedence over theme classes (via `twMerge`). ## Prefer props over Tailwind classes VaneUI provides boolean props for most common styling needs. Use these instead of Tailwind classes to ensure consistent theming and proper integration with the size system. ### Common props vs Tailwind classes | Instead of Tailwind | Use VaneUI prop | |---------------------|-----------------| | `className="uppercase"` | `uppercase` | | `className="font-bold"` | `fontBold` | | `className="font-semibold"` | `fontSemibold` | | `className="font-mono"` | `fontMono` | | `className="italic"` | `italic` | | `className="text-center"` | `textCenter` | | `className="sticky"` | `sticky` | | `className="items-center"` | `itemsCenter` | | `className="justify-between"` | `justifyBetween` | | `className="border-b"` | `borderB` | | `className="rounded-full"` | `pill` | | `className="shadow"` | `shadow` | ```tsx // Good: Using VaneUI props IMPORTANT Card with bottom border Spaced content // Avoid: Tailwind classes that duplicate props IMPORTANT Card with bottom border Spaced content ``` ### Gap is controlled by size props Don't use Tailwind gap classes. Gap is controlled by the size prop system (`xs`, `sm`, `md`, `lg`, `xl`) and is enabled by default on layout components. ```tsx // Good: Size prop controls gap Large gap between items Small gap between items Default gap (md) // Avoid: Tailwind gap classes Don't override gap this way ``` ### Background colors via appearance props Background colors are controlled by appearance + variant props. Only use `bg-*` classes for edge cases not covered by the theme (like gradients). ```tsx // Good: Appearance controls color Primary background // Avoid: Tailwind bg classes when appearance works Don't fight the theme // OK: Tailwind for custom colors not in theme
Custom gradient
``` ### Typography styling is built-in Typography components have built-in letter spacing, line height, and other typographic properties. Avoid overriding these with Tailwind classes. ```tsx // Good: Built-in typography styling STATUS Properly styled heading // Avoid: Manual typography overrides STATUS Overridden heading ``` # Next.js > Set up VaneUI in a Next.js App Router project: add the Tailwind CSS v4 imports, render ThemeProvider in the root layout, and add dark mode without a flash. Source: https://vaneui.com/docs/getting-started/nextjs Import: `import { Button, ThemeProvider } from "@vaneui/ui"` This guide sets up VaneUI in a Next.js project that uses the App Router and Tailwind CSS v4. It covers four pieces: the CSS entry, the root layout, an optional client wrapper for theme functions, and an inline dark-mode script. ## Create a Next.js app with Tailwind CSS Start from a Next.js app with Tailwind CSS, then add VaneUI: ```bash npx create-next-app@latest my-app --typescript --tailwind --app cd my-app npm install @vaneui/ui ``` For an existing app without Tailwind CSS, install it with `npm install -D tailwindcss @tailwindcss/postcss` and add `"@tailwindcss/postcss": {}` to the plugins in `postcss.config.mjs`. ## Import the VaneUI styles Replace the contents of `app/globals.css`: ```css @import "tailwindcss"; @import "@vaneui/ui/tokens"; @import "@vaneui/ui/vars"; @source "../node_modules/@vaneui/ui"; ``` The `@source` line lets Tailwind find the classes VaneUI components use. See [Installation](/docs/getting-started/installation) for the prebuilt stylesheet if you don't use Tailwind CSS. ## Wrap the root layout in ThemeProvider VaneUI components ship with the `'use client'` directive, so a Server Component such as the root layout can render them directly: ```tsx // app/layout.tsx import { ThemeProvider } from '@vaneui/ui'; import './globals.css'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` `themeDefaults` and `extraClasses` take plain objects, so the layout can pass them too. `themeOverride` takes a function, and a Server Component can't pass a function to a client component. Put it in a client wrapper, then render `AppThemeProvider` in the layout instead of `ThemeProvider`: ```tsx // app/theme-provider.tsx 'use client'; import { ThemeProvider, type ThemeProps } from '@vaneui/ui'; const override = (theme: ThemeProps) => theme; export function AppThemeProvider({ children, }: { children: React.ReactNode; }) { return {children}; } ``` ## Dark mode without a flash VaneUI switches to dark colors when `` has `data-theme="dark"`. Set it before the page paints with a script in the root layout's ``, so a dark-mode visitor never sees a white flash: ```tsx ``` ## Theming one subtree To darken a region of an otherwise light page, set `data-theme="dark"` on a wrapping element. The dark token values inherit through that subtree: ```tsx
Dark card on an otherwise light page
``` The wrapper `
` paints nothing on its own. Give the subtree a surface: a [Card](/docs/layout-components/card), or a [Section](/docs/layout-components/section) with an explicit appearance (`
`). Either reads the dark background token and fills the area; a bare `Section` paints nothing. ## Light regions inside a dark page `data-theme="light"` on an element inside a dark page or a dark wrapper makes that subtree light. It sets `color-scheme: light`, so every pair resolves to its light value, and it resets the shadow alphas. Use it for a light preview panel, an embedded document or a sample that must stay light whatever the page theme. Before 1.4.5, it only reset `color-scheme`, and the dark colors still came through. ```tsx demo
Dark surface
Light card inside the dark surface
``` The demo renders the same in both site themes: the outer wrapper is always dark and the inner one always light. On `` itself, `data-theme="light"` changes nothing, since the light rule skips the root element. Token customizations for the light page keep working when a theme toggle sets `data-theme="light"` on ``. ## Overlays follow the page theme [Modal](/docs/overlay-components/modal), `Popup`, and `Overlay` render through a portal at the end of ``, so they resolve the theme set on `` or ``, not the theme of an inner wrapper. Subtree theming covers in-flow content. For overlays, `` is the primary supported mode. ## Changing a color in both schemes A token you re-declare on `:root` alone (as in the [CSS variables global override](/docs/customization/css-variables)) reaches pages without `data-theme`, but the scheme rules declare it again on dark elements and light subtrees, so the change does not show on a dark page or in a light region. To change a color in both schemes, declare it as a `light-dark()` pair on the `data-theme` elements, and keep the light value on `:root` for pages without `data-theme`: ```css /* after the @vaneui/ui stylesheet */ :root { --color-bg-info: oklch(95% 0.02 220); } :root[data-theme], [data-theme]:not(:root) { --color-bg-info: light-dark(oklch(95% 0.02 220), oklch(30% 0.05 220)); } ``` `:root[data-theme]` covers `` and `[data-theme]:not(:root)` covers subtree wrappers. Both selectors carry the same weight as the built-in scheme rules, so the rule wins when it comes after the VaneUI stylesheet, including in a light region inside a dark page. Keep `light-dark()` pairs off a `:root`-only rule. Bundlers that compile `light-dark()` for older browsers resolve it where the variable is declared, so a pair declared once on `:root` would not switch inside nested `data-theme` subtrees. ## Browser support for light-dark() Uncompiled, `light-dark()` needs Chrome 123, Firefox 120 or Safari 17.5. Bundlers that compile it for older browsers keep those browsers working, and Next.js does so by default. The pairs apply only on `data-theme` elements, so a page without `data-theme` does not depend on `light-dark()` support. ## Custom named themes The dark scheme is one instance of a general pattern: re-declare any `--color-*` token under a selector of your choice. A named theme works the same way: ```css [data-theme="ocean"] { --color-bg-info: oklch(95% 0.02 220); --color-text-info: oklch(45% 0.12 220); } ``` Apply it with `data-theme="ocean"` on any element, and the subtree reads the overridden tokens. # Customizing Styles > Override VaneUI React component styles with Tailwind CSS classes in className: which properties win, when a CSS variable or a prop fits better, and pitfalls. Source: https://vaneui.com/docs/customization/customizing-styles Import: `import { Badge, Button, Card, Divider, Img, Input, Row, Text } from "@vaneui/ui"` VaneUI components are styled through boolean props and [CSS variables](/docs/customization/css-variables), but you can override any visual property using standard Tailwind CSS classes via the `className` prop. This page explains how overrides work, when to use each approach, and common pitfalls. ## How className overrides work VaneUI uses [`tailwind-merge`](https://github.com/dcastil/tailwind-merge) to combine theme-generated classes with your `className`. When a conflict exists, **your class always wins** because `className` is passed last: ```tsx // Internally, VaneUI does: const finalClasses = twMerge(...themeGeneratedClasses, className); ``` This means the conflict is resolved at the **JavaScript level**: the losing class is removed from the string entirely before the DOM renders. There are no CSS specificity wars or `!important` hacks. ## Properties className can override VaneUI components use CSS variable consumer classes like `border-(--border-color)`, `text-(--text-color)`, and `bg-(--bg-color)`. These compile to standard CSS (`border-color: var(--border-color)`). Any Tailwind utility that targets the same CSS property will override them. ### Border color ```tsx // VaneUI generates: border-(--border-color) // Your class: border-red-300 // Result: border-red-300 (VaneUI class removed) Custom border ``` ### Text color ```tsx // VaneUI generates: text-(--text-color) // Your class: text-emerald-600 // Result: text-emerald-600 Custom text color ``` ### Background color ```tsx // VaneUI generates: bg-(--bg-color) // Your class: bg-gradient-to-r from-blue-500 to-purple-500 // Result: bg-gradient-to-r from-blue-500 to-purple-500 Gradient card ``` ### Hover, active, and focus states State-specific overrides work too. Each state variant is resolved independently: ```tsx // Override hover background // Override focus ring // Override active state ``` ### Shadow ```tsx Extra large shadow No shadow ``` ### Non-conflicting properties are preserved `twMerge` only removes classes that target the **same CSS property**. Different properties coexist: ```tsx // border-width and border-color are separate properties // VaneUI's border-width stays, only color is overridden {/* Has both: border-[length:var(--bw)] (width) + border-red-300 (color) */} ``` ## Override hierarchy From lowest to highest priority: | Level | Mechanism | Scope | Use When | |-------|-----------|-------|----------| | 1 | Component defaults | Library-wide | You're the library author | | 2 | `themeDefaults` | Provider subtree | Change defaults for all components in a section | | 3 | `extraClasses` | Provider subtree | Add classes based on active props | | 4 | `themeOverride` | Provider subtree | Modify theme internals programmatically | | 5 | **`className`** | Single instance | One-off style adjustments | | 6 | `style` | Single instance | Runtime-computed values that can't be a class | For most use cases, `className` (level 5) is the right tool. Use `themeDefaults` or `extraClasses` when you need the override to apply across many components. ## CSS variable approach Instead of replacing the consumer class with a utility like `border-red-300`, set the underlying CSS variable with an arbitrary-property class. This keeps VaneUI's consumer class active but changes the value it reads: ```tsx // Set the variable with an arbitrary-property class: the component's own // border-(--border-color) still fires, but reads your custom value instead of the theme's Theme-aware border override ``` **Replacing the utility vs setting the variable:** - **`className="border-red-300"`**: static override, ignores the theme entirely. The component's `border-(--border-color)` class is removed by twMerge. - **`className="[--border-color:...]"`**: theme-aware override. The component still uses its consumer class, but the variable value is different. Children inherit the new value. The CSS variable approach is especially useful when you want [descendants to inherit](/docs/customization/variant-inheritance) your override: ```tsx // All children of this Card inherit the custom border color {/* This Input also gets a red border */} {/* Red divider too */} ``` ## When to use VaneUI props instead VaneUI provides [boolean props](/docs/getting-started/core-concepts) for most common styling needs. Always prefer props over Tailwind classes when a prop exists. This ensures the styling integrates with the size system, responds to theme changes, and works with `ThemeProvider` overrides. | Instead of Tailwind | Use VaneUI Prop | |---------------------|-----------------| | `className="font-bold"` | `fontBold` | | `className="text-center"` | `textCenter` | | `className="uppercase"` | `uppercase` | | `className="rounded-full"` | `pill` | | `className="flex items-center"` | `flex itemsCenter` | | `className="cursor-pointer"` | `cursorPointer` | | `className="gap-4"` | Use size props (`xs` through `xl`) | | `className="bg-red-500"` | `danger filled` | ## className is for things VaneUI props don't cover Good uses of `className`: ```tsx {/* Custom width/height */} Fixed-width card {/* Stacking order: the sticky prop already pins the row to the top edge */} Sticky header {/* Animations */} Live {/* Gradients */} Gradient card {/* Aspect ratio */} Banner ``` ## className pitfalls to avoid ### Don't override gap with Tailwind classes Gap is controlled by VaneUI's size system. Use size props instead: ```tsx // Wrong: fights the size system Content // Right: gap scales with size Content Tight content ``` ### Don't override padding with Tailwind classes Padding is also size-driven and scales proportionally: ```tsx // Wrong Content // Right: padding scales with size Content ``` ### Don't use appearance-colored Tailwind classes when an appearance prop exists ```tsx // Wrong: bypasses the theme system // Right: uses the theme system ``` ### Beware of className on typography components Typography components have built-in letter spacing, line height, and font size that scale with the size prop. Overriding these with Tailwind classes breaks the proportional scaling: ```tsx // Wrong: breaks size scaling Content // Right: use size props Content ``` # Variant Inheritance > Which VaneUI React components paint their own colors and which inherit them from a parent, why explicit props win, and how inheritAppearance works. Source: https://vaneui.com/docs/customization/variant-inheritance Import: `import { Badge, Card, Code, Link, Stack, Text, ThemeProvider, Title } from "@vaneui/ui"` 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](/docs/customization/css-variables). 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: 1. **`:root` fallbacks**: set `--text-color`, `--bg-color`, `--border-color` etc. to the primary outline palette (dark text, white background, light border). Inherit-mode components fall back to these when no ancestor sets colors. 2. **Direct CSS rules**: when a component emits `data-appearance` (and `data-variant`), a CSS rule fires and sets those variables on the element and its subtree. Every component with a concrete appearance emits these. 3. **CSS cascade**: inherit-mode components set no variables of their own, so they read them from the nearest ancestor that did. ``` :root → --text-color: dark, --bg-color: white → --text-color: white, --bg-color: dark (emits attrs, own rule fires)