# Introduction
> What XiodUI is, how it's shipped, and its licence.
Source: https://ui.xiod.dev/docs/overview/introduction
## What is XiodUI?
XiodUI is a React component library: 90 styled, accessible components built on [Base UI](https://base-ui.com) and Tailwind CSS 4. Base UI provides the behaviour (keyboard support, focus management, screen reader semantics); XiodUI adds the design, the animations and components Base UI doesn't have, from a date picker to a file uploader.
```tsx title="app/page.tsx"
import { Button } from "xiod-ui/button";
export default function Page() {
return ;
}
```
## How it differs
- **Animations are CSS.** Every transition is a CSS transition or keyframe. There is no animation library to install, and none runs in the browser.
- **Styles compile ahead of time.** Variants are Tailwind classes, so nothing calculates styles at runtime.
- **Themes are stylesheets.** 20 ready-made palettes, each with light and dark modes. Import one, or let users switch between them at runtime.
- **Touch targets are 44px.** On touch screens, small controls get a 44px hit area to meet WCAG 2.5.5.
- **Your icons, if you want them.** Every built-in icon can be replaced, one at a time or app-wide.
## How it's shipped
XiodUI is one versioned npm package. There is no CLI, no generator, and nothing to copy into your repository.
- **One install, every component.** `npm install xiod-ui` gives you all 90, and upgrades arrive like any other dependency.
- **One import path per component.** `import { Button } from "xiod-ui/button"`. There is no root import, so only the components you use reach your bundle.
- **Base UI and XiodIcons included.** They are dependencies of `xiod-ui`, so they upgrade with it. You install React and React DOM yourself.
- **One stylesheet.** `@import "xiod-ui/styles"` brings the design tokens and the `dark` variant. No config file, no plugin.
- **Typed, and ready for server components.** Type declarations for every component and prop, and `"use client"` where a component needs it.
## Licence
XiodUI is open source under [PolyForm Perimeter 1.0.1](https://polyformproject.org/licenses/perimeter/1.0.1). Use it in any project, commercial or not, open or closed source; nothing you build with it has to be published. The one restriction: you may not use XiodUI to build a product that competes with it, such as another component library or UI kit.
---
# Installation
> Install the package, add the stylesheet, and import your first component.
Source: https://ui.xiod.dev/docs/overview/installation
## Requirements
- React and React DOM 19.3 or later
- Tailwind CSS 4.3 or later, already set up in the project ([Tailwind's installation guide](https://tailwindcss.com/docs/installation))
- Node.js 20 or later
## Install
```bash
npm install xiod-ui
```
Base UI and XiodIcons are dependencies of `xiod-ui` and install with it. Don't install `@base-ui/react` yourself: a second copy breaks popups and portals.
## Add the stylesheet
Import XiodUI's stylesheet after Tailwind in your global CSS.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
```
This adds the colour tokens and the `dark` variant, and tells Tailwind to generate the classes the components use. Without it, components render unstyled. There is no config file or plugin to add.
## Use a component
Import each component from its own path, the component's name in kebab-case.
```tsx title="app/page.tsx"
import { Button } from "xiod-ui/button";
export default function Page() {
return ;
}
```
There is no root import: `import { Button } from "xiod-ui"` doesn't resolve. Components that need the browser already carry `"use client"`, so you can render them from a server component.
## Icons
To use XiodIcons in your own markup, add the package directly:
```bash
npm install xiod-icons
```
```tsx title="components/AddButton.tsx"
import { PlusSign } from "xiod-icons";
import { Button } from "xiod-ui/button";
export function AddButton() {
return (
);
}
```
To replace the icons components draw themselves, see [Icons](https://ui.xiod.dev/docs/styling/icons).
## Hooks
The two hooks the components use are exported too, from `xiod-ui/hooks/…`.
`useCopyToClipboard` copies text and reports it. `copyToClipboard` resolves to whether the copy succeeded, and `isCopied` stays `true` for `timeout` milliseconds (2000 by default; `0` keeps it). It works on plain `http` pages, where `navigator.clipboard` doesn't exist. When the browser refuses the copy, `onError` receives the error; without it, the error is logged.
```tsx title="components/CopyCommand.tsx"
"use client";
import { Button } from "xiod-ui/button";
import { useCopyToClipboard } from "xiod-ui/hooks/use-copy-to-clipboard";
export function CopyCommand({ command }: { command: string }) {
const { isCopied, copyToClipboard } = useCopyToClipboard({
onError: () => alert("Couldn't copy. Select the text instead."),
});
return (
);
}
```
`useMediaQuery` returns whether a media query matches, and follows it as it changes. Pass a breakpoint name (`"lg"`, `"max-md"`, `"sm:max-lg"`), an object (`{ min: "md", pointer: "coarse" }`), or any CSS media query. `useIsMobile()` is `useMediaQuery("max-md")`. The named breakpoints are Tailwind's, except `md`, which is 800px rather than 768px.
```tsx
import { useIsMobile, useMediaQuery } from "xiod-ui/hooks/use-media-query";
const isMobile = useIsMobile();
const isWide = useMediaQuery("xl");
```
On the server there is no viewport, so both return `false` there, and a server-rendered page switches to the real value as it hydrates. Anything that must be right on the first paint belongs in responsive classes instead.
## AI agents
Coding agents tend to guess at this library's API from other libraries. Install the XiodUI Agent Skill so they use the real one:
```bash
npx skills add ImKKingshuk/XiodUI
```
See [Agent Skill](https://ui.xiod.dev/docs/overview/agent-skill) for what it covers, including guides for migrating from shadcn/ui, HeroUI, MUI, Ant Design, Mantine and Chakra UI.
---
# Agent Skill
> Teach coding agents XiodUI's real API, and migrate from other libraries.
Source: https://ui.xiod.dev/docs/overview/agent-skill
## Why a skill
Coding agents guess at libraries they don't know well, and the guesses come from other libraries: a root `from "xiod-ui"` import, `asChild`, an `npx … add` step, shadcn part names such as `DialogContent`. None of those exist in XiodUI, so the code the agent writes doesn't build.
The XiodUI Agent Skill gives the agent the library's real API before it writes anything. It is maintained in the library repo alongside the components, so it describes the version you install.
## Install
```bash
npx skills add ImKKingshuk/XiodUI
```
The [`skills`](https://skills.sh) CLI installs it for Claude Code, Codex, Cursor, GitHub Copilot and dozens of other agents. Run it inside a project to install it there, or add `-g` to install it for every project.
The agent loads the skill on its own when a task mentions XiodUI or `xiod-ui`, imports from the package, or asks to migrate from another UI library. Nothing needs to be pasted into the prompt.
## What it covers
- **Setup.** The one install command, the two CSS imports, and that there is no config file, plugin or CLI.
- **Imports.** Every component's subpath (`xiod-ui/input-otp`, not `xiod-ui/InputOtp`), and why `@base-ui/react` is never installed separately.
- **Composition.** Part names, `render` in place of `asChild`, data-driven components such as Select, Combobox and Command, and the Base UI state attributes (`data-open`, `data-checked`) to style against.
- **Theming.** Dark mode with `ThemeProvider`, palettes, runtime palette switching, overriding tokens, and swapping built-in icons.
- **Every component.** A reference file per component with its parts, props and an example, read only when the task uses that component.
- **Mistakes to avoid.** A list of the things agents most often get wrong here, from `tailwind.config.js` colour blocks to adding a second animation library.
## Migrating from another library
The skill includes a step-by-step guide for moving an existing app to XiodUI from each of these libraries:
| Library | Detected by |
| :------------ | :-------------------------------------------------- |
| shadcn/ui | `components.json`, `components/ui/*`, `@radix-ui/*` |
| HeroUI/NextUI | `@heroui/react`, `@nextui-org/react` |
| MUI | `@mui/material` |
| Ant Design | `antd` |
| Mantine | `@mantine/core` |
| Chakra UI | `@chakra-ui/react` |
Each guide maps the old library's components, part names and props to XiodUI's, and replaces its toast, theme, form and icon helpers. They all follow the same order: list the files that use the old library, set XiodUI up next to it, migrate one file at a time and build after each, then uninstall the old library and check light, dark and phone-width layouts.
To start, ask the agent something like:
```text
Migrate this app from shadcn/ui to XiodUI.
```
> [!NOTE]
> Where a component has no XiodUI equivalent, the guide says so and what to use instead. The agent is told never to invent a component or prop to fill the gap.
## Without the skill
For an agent that can't install skills, point it at the documentation directly:
- [`/llms.txt`](https://ui.xiod.dev/llms.txt) lists every page of these docs.
- [`/llms-full.txt`](https://ui.xiod.dev/llms-full.txt) is the whole documentation in one file.
- Every page is also available as Markdown at its own URL plus `.md`, and the **Copy page** button at the top of each page copies it.
---
# Colors
> Every colour token, what it's for, and how tokens pair up.
Source: https://ui.xiod.dev/docs/styling/colors
## How the tokens work
Components never hard-code a colour. Every one of them reads a CSS custom property — a _token_ — defined in `xiod-ui/styles`. Change a token and everything that uses it changes with it, which is the whole of how theming works here.
> [!NOTE]
> **Tokens come in pairs**
> A token names a surface. The matching `-foreground` token is what goes _on_ that surface. `--card` is the panel; `--card-foreground` is the text on the panel. Whenever you change one, look at the other.
> [!NOTE]
> **Each token is a Tailwind class**
> `--primary` gives you `bg-primary`, `text-primary`, `border-primary`, and so on — the token name minus the dashes. Your own markup can use them exactly like the components do.
```tsx title="Anywhere in your app"
/* Every token is available as a Tailwind utility, on any property
that takes a colour. There is no xiodui- prefix. */
{/* opacity works too */}
/* And in plain CSS, if you prefer: */
.my-panel {
background: var(--card);
color: var(--card-foreground);
}
```
Values can be written in any CSS colour syntax — `#0f6b72`, `oklch(0.55 0.21 264)`, `hsl(...)`, even `color-mix()`. The library uses a mix of all of them internally. Nothing is parsed at build time, so there is no format to conform to.
To change any of them, see the [Customization](https://ui.xiod.dev/docs/styling/customization) page. This page is the reference for what exists.
## Surfaces
The three layers a page is built from: the page itself, panels raised off it, and floating overlays like menus and popovers.
| Name | Token | Text on it |
| --- | --- | --- |
| Background | `--background` | `--foreground` |
| Card | `--card` | `--card-foreground` |
| Popover | `--popover` | `--popover-foreground` |
## Theme colors
`--primary` is your brand colour — it drives the default button, links, and focus emphasis. `--muted` and `--accent` are the quiet greys behind hover states and secondary text. If you only ever change one token, change `--primary`.
| Name | Token | Text on it |
| --- | --- | --- |
| Primary | `--primary` | `--primary-foreground` |
| Secondary | `--secondary` | `--secondary-foreground` |
| Muted | `--muted` | `--muted-foreground` |
| Accent | `--accent` | `--accent-foreground` |
## State colors
Error, information, success, and warning. Each has a `-foreground` partner, but it does not mean what it means above — see the note under the swatches.
| Name | Token | Text on it |
| --- | --- | --- |
| Destructive | `--destructive` | – |
| Info | `--info` | – |
| Success | `--success` | – |
| Warning | `--warning` | – |
> [!WARNING]
> **The state `-foreground` tokens are the exception**
> `--destructive-foreground` is not the text on a solid destructive button — that is always white. It is a darker shade of the same hue, meant for text on a faint tint of the colour, which is how alerts and outline buttons are drawn. The same holds for `--info`, `--success` and `--warning`. Set it to a readable shade of its own colour, not to a contrasting one.
## Lines and focus
`--border` is every divider and outline, `--input` the slightly stronger edge on form fields, and `--ring` the keyboard focus ring. These are usually near-transparent, so they may look almost invisible below.
| Name | Token | Text on it |
| --- | --- | --- |
| Border | `--border` | – |
| Input | `--input` | – |
| Ring | `--ring` | – |
## Corner radius
Not a colour, but the same idea. `--radius` is a single length that every component derives its corners from, so one value sets how round or how square the whole library looks.
- 0rem — square
- 0.625rem — default
- 1.25rem — round
Components step off it rather than using it raw — a small badge uses `calc(var(--radius) - 4px)` and a card the full value — so the proportions hold at every size. Each shipped palette sets its own `--radius` to suit its character.
## Sidebar colors
A parallel set used only by the sidebar, so app navigation can sit on its own surface without dragging the rest of the page with it. If you do not use the sidebar, you can ignore these entirely.
| Name | Token | Text on it |
| --- | --- | --- |
| Sidebar | `--sidebar` | `--sidebar-foreground` |
| Sidebar Primary | `--sidebar-primary` | `--sidebar-primary-foreground` |
| Sidebar Accent | `--sidebar-accent` | `--sidebar-accent-foreground` |
`--sidebar-border` and `--sidebar-ring` complete the set, matching `--border` and `--ring`.
## MorphicToast
MorphicToast has its own tokens for its motion and state colours. Override them like any other token.
| Token | Controls |
| :------------------------ | :--------------------------------------------------------------------------------------- |
| `--morphic-duration` | How long the toast takes to morph (600ms) |
| `--morphic-spring-easing` | The easing curve of the morph |
| `--morphic-state-` | The colour for each state: `success`, `error`, `warning`, `info`, `loading` and `action` |
---
# Typography
> The Text component's type scale, weights and variants.
Source: https://ui.xiod.dev/docs/styling/typography
## The Text component
`Text` from `xiod-ui/text` applies the library's type scale, so headings and body copy match the components around them without repeating utility classes. It sets size, weight and colour only. The font comes from your app's `font-sans` and `font-mono`, and XiodUI doesn't ship a font.
```tsx title="components/ArticleHeader.tsx"
import { Text } from "xiod-ui/text";
export function ArticleHeader() {
return (
Release notesWhat changed in this version, and how to upgrade.
Updated 2 hours ago · 4 min read
);
}
```
`Text` renders a `
` by default. The heading variants render `
`–`
` and the mono variants a ``. Pass `render` for any other element (see [below](#polymorphic-rendering)).
## Headings
Four heading variants, each semibold with slightly tight letter-spacing.
| Variant | Element | Size | Use |
| --- | --- | --- | --- |
| `heading1` | `
` | 18px / 1.125rem | Widget, group header |
All four are semibold with `-0.025em` tracking.
## Body sizes
`size` sets the body text size. Sizes are one step larger on phones, where text is read at arm's length, and one step smaller from the `sm` breakpoint up.
| Props | Size | Use |
| --- | --- | --- |
| `size="xl"` | 18px (20px on phones) | Lead, editorial intro |
| `size="lg"` | 16px (18px on phones) | Subtitle, callout |
| `size="default"` | 14px (16px on phones) | Standard paragraph |
| `variant="secondary" size="sm"` | 12px (14px on phones) | Meta details, helper text |
| `size="xs"` | 12px | Footnote, legal, micro-copy |
## Weights
`weight` is `normal`, `medium`, `semibold` or `bold`, and combines with any size.
Every weight — `normal` (400), `medium` (500), `semibold` (600), `bold` (700) — combines with every size: `xl`, `lg`, `default`, `sm`, `xs`.
## Colour variants
`secondary` is muted text for metadata and helper copy. `success`, `info`, `warning` and `error` use the matching state colour, for inline feedback such as a field's validation message.
`success`, `info`, `warning` and `error` colour text with the matching state token, for alerts, status badges and form feedback.
## Monospace
`mono` and `mono-secondary` set text in `font-mono`, for commands, paths, IDs and values.
- `variant="mono"` — primary monospace for commands, paths and values.
- `variant="mono-secondary"` — subdued monospace for hashes, IDs and metadata.
## Rendering another element
Pass an element to `render` to keep the styles on a different tag, with that element's own props.
```tsx title="components/EmailLabel.tsx"
import { Text } from "xiod-ui/text";
export function EmailLabel() {
return (
}>
Email address
);
}
```
A heading variant with `render` changes the tag without changing the look, so a visually large heading can still be an `
` in the page outline:
```tsx
}>
Pricing
```
## Truncation
`truncate` keeps text on one line and ends it with an ellipsis when it runs out of room, including inside flex layouts.
```tsx title="components/ProjectName.tsx"
import { Text } from "xiod-ui/text";
export function ProjectName({ name }: { name: string }) {
return (
{name}
);
}
```
---
# Themes
> Ready-made palettes, and letting users switch between them.
Source: https://ui.xiod.dev/docs/styling/themes
## What a theme is
A theme is a full set of the tokens on the [Colors](https://ui.xiod.dev/docs/styling/colors) page, plus `--radius`, in one stylesheet. Import it and every component follows, because components only read the tokens.
20 palettes ship with the library, in two families. Each one defines both colour schemes, so a theme and dark mode are independent choices rather than a grid you have to maintain.
## Apply one theme
Import a palette after `xiod-ui/styles`. It overrides the base tokens outright, so the whole app is that palette and nothing in your markup changes.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
/* One line. Every token from the Colors page is redefined, light and dark. */
@import "xiod-ui/themes/cobalt";
```
Order matters: the import has to come after `xiod-ui/styles`, or the base tokens win. You can still override individual tokens below the import — the palette is a starting point, not a lock.
## Offer a choice
To let your own users pick, import the `/scoped` variant of each palette instead. A scoped stylesheet only applies under `[data-palette="name"]`, so several can coexist without fighting over the base tokens.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
/* Scoped: each palette applies only under its own data-palette attribute,
so importing several is safe — none of them touch the base tokens. */
@import "xiod-ui/themes/cobalt/scoped";
@import "xiod-ui/themes/lagoon/scoped";
@import "xiod-ui/themes/riso/scoped";
```
Then switch palettes with `setPalette` from `useTheme`. It writes the attribute to ``, remembers the choice in localStorage and suppresses transitions during the swap, the same way the light/dark toggle does. A returning visitor's palette applies before the page first paints, like their light or dark choice, so it never flashes the default palette first. This needs `ThemeProvider` in your root layout — see the [Dark Mode](https://ui.xiod.dev/docs/styling/dark-mode) page for that setup.
```tsx title="components/PaletteSwitcher.tsx"
"use client";
import { Button } from "xiod-ui/button";
import { useTheme } from "xiod-ui/theme-provider";
const PALETTES = [
{ value: "cobalt", label: "Cobalt" },
{ value: "lagoon", label: "Lagoon" },
{ value: undefined, label: "Default" },
];
export function PaletteSwitcher() {
const { palette, setPalette } = useTheme();
return (
{PALETTES.map(({ value, label }) => (
))}
);
}
```
Import only the palettes you actually offer. Each scoped stylesheet is about 1KB gzipped, but they all ship on every page.
## The palettes
Every card below is painted by that palette's real tokens, in whichever colour scheme you are currently reading in. Press _Try it_ to apply one to this entire site.
**Core** — Restrained enough to ship as-is.
- Neutral (the base, no import): The base theme, shipped with the library.
- Clay (`xiod-ui/themes/clay`): Warm cream and terracotta.
- Sepia (`xiod-ui/themes/sepia`): Aged paper and tanned leather.
- Sage (`xiod-ui/themes/sage`): Muted garden green.
- Lagoon (`xiod-ui/themes/lagoon`): Deep teal on cool mineral.
- Cobalt (`xiod-ui/themes/cobalt`): Deep cobalt blue on cool grey.
- Iris (`xiod-ui/themes/iris`): Indigo and violet on soft lilac.
- Roast (`xiod-ui/themes/roast`): Dark coffee and steamed cream.
- Crimson (`xiod-ui/themes/crimson`): Oxblood, olive and steel on gunmetal.
- Sorbet (`xiod-ui/themes/sorbet`): Rose, sky and citrus.
**Expressive** — Louder, with a point of view.
- Cel (`xiod-ui/themes/cel`): Sky blue and marigold, flat and high-key.
- Afterglow (`xiod-ui/themes/afterglow`): Sunset warmth by day, cerulean by night.
- Ion (`xiod-ui/themes/ion`): Violet and readout green on instrument grey.
- Cathode (`xiod-ui/themes/cathode`): Hot magenta and phosphor green on cold concrete.
- Riso (`xiod-ui/themes/riso`): Magenta and cyan misprint on newsprint.
- Taffy (`xiod-ui/themes/taffy`): Watermelon, sky and lemon.
- Mochi (`xiod-ui/themes/mochi`): Milky orchid, mint and blush.
- Blacklight (`xiod-ui/themes/blacklight`): Ultraviolet, acid green and hot pink.
- Toxin (`xiod-ui/themes/toxin`): Biohazard green and blood on wet concrete.
- Cinder (`xiod-ui/themes/cinder`): Molten orange on cold ash.
## What is guaranteed
Both families are held to the same bar. The difference is how much personality a palette imposes, not how carefully it was built.
- **Readable text.** Every `*-foreground` token clears 4.5:1 against the surface it actually sits on — including the state foregrounds, which are checked against their own colour tinted over the background at the opacity components really use.
- **Usable controls.** White text clears 4.5:1 on the solid `destructive` fill, and that fill clears 3:1 against the background, so a destructive button is legible as a control and not only as text.
- **Distinct from each other.** No two palettes' primaries sit closer than 0.055 in OKLab, a shade above the range where two colours read as one. Palettes may share a hue; none should be mistakable for another at a glance.
The library's build checks all three, so a palette that fails one can't ship.
## Rolling your own
A palette is only a stylesheet of tokens, so you can start from the closest one and override the tokens that differ below its import. [Customization](https://ui.xiod.dev/docs/styling/customization#start-from-palette) shows how, with the full token set as a template.
---
# Customization
> Override tokens for the whole app, one section, or one element.
Source: https://ui.xiod.dev/docs/styling/customization
## There is only one mechanism
Every colour, and the corner radius, is a CSS custom property. Components read those properties and nothing else. So customising XiodUI means redefining the properties you care about in your own stylesheet — no config file, no plugin, no wrapper components, and nothing to eject from.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
/* Your overrides go below the import. That is the entire mechanism. */
:root {
--primary: #4f46e5;
}
```
That is a complete, working customisation. Every button, link, and focus ring in the app is now indigo. If you only read one section of this page, this was it.
## Two rules that trip people up
> [!NOTE]
> **Overrides go after the import**
> CSS gives the last matching rule the win. Put your `:root` block above `@import "xiod-ui/styles"` and the library defaults simply overwrite it. Imports also have to sit at the top of the file, so in practice: imports first, your rules after.
> [!NOTE]
> **Use :root, not @theme**
> `@theme` is where Tailwind defines its own scales. XiodUI's tokens are already wired into Tailwind for you, so they are set on `:root` like ordinary CSS. Putting them in `@theme` quietly does nothing.
## Setting your brand colour
A colour is rarely alone. `--primary` is the fill; `--primary-foreground` is the label printed on that fill, and `--ring` is the focus outline that should match. Set all three, in both colour schemes.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
:root {
--primary: #4f46e5; /* the button fill */
--primary-foreground: #ffffff; /* the label on that fill */
--ring: #4f46e5; /* the focus ring, so it matches */
@variant dark {
--primary: #a5b4fc; /* lighter, so it reads on a dark page */
--primary-foreground: #1e1b4b; /* dark text, because the fill is now light */
--ring: #a5b4fc;
}
}
```
> [!IMPORTANT]
> **Do not skip the dark values**
> Tokens you set only in `:root` apply in dark mode too, because nothing overrides them back. A brand colour picked to sit on white usually goes muddy on a dark page — most palettes use a lighter, less saturated version at night, and flip the foreground from white to dark to match.
>
> Put them in `@variant dark` rather than a separate `.dark { … }` rule. The variant matches from the first paint, while `ThemeProvider` puts the `dark` class on `` only once React has hydrated, so a plain `.dark` rule would briefly show your light values to dark-mode visitors.
## Start from a palette
Writing all thirty-odd tokens by hand is real work, and most of it is deciding on greys. It is usually faster to import one of the shipped palettes and change the few tokens you disagree with — the import is just a stylesheet, so your rules below it still win.
```css title="app/globals.css"
@import "tailwindcss";
@import "xiod-ui/styles";
/* Start from a palette that is already close... */
@import "xiod-ui/themes/cobalt";
/* ...then change only what you disagree with. */
:root {
--radius: 0.25rem;
--primary: #1d4ed8;
@variant dark {
--primary: #93c5fd;
}
}
```
## Or start from scratch
If you would rather define everything yourself, this is the complete set. Copy it below the import and edit in place — anything you leave out falls back to the library default, so you can also delete the parts you do not care about.
```css title="app/globals.css"
:root {
--radius: 0.625rem;
/* Surfaces */
--background: #ffffff;
--foreground: #262626;
--card: #ffffff;
--card-foreground: #262626;
--popover: #ffffff;
--popover-foreground: #262626;
/* Theme */
--primary: #262626;
--primary-foreground: #fafafa;
--secondary: #f5f5f5;
--secondary-foreground: #262626;
--muted: #f5f5f5;
--muted-foreground: #737373;
--accent: #f5f5f5;
--accent-foreground: #262626;
/* States — the -foreground is text on a TINT of the colour, not on the
solid fill. Keep it the same hue, just darker. */
--destructive: #ef4444;
--destructive-foreground: #b91c1c;
--info: #3b82f6;
--info-foreground: #1d4ed8;
--success: #10b981;
--success-foreground: #047857;
--warning: #f59e0b;
--warning-foreground: #b45309;
/* Lines and focus */
--border: #e5e5e5;
--input: #d4d4d4;
--ring: #a3a3a3;
/* Sidebar — safe to delete if you do not use it */
--sidebar: #fafafa;
--sidebar-foreground: #525252;
--sidebar-primary: #262626;
--sidebar-primary-foreground: #fafafa;
--sidebar-accent: #f5f5f5;
--sidebar-accent-foreground: #262626;
--sidebar-border: #ebebeb;
--sidebar-ring: #a3a3a3;
@variant dark {
/* Every token above, again, with dark-scheme values. Anything you leave
out here keeps the library's dark default. */
--background: #0a0a0a;
--foreground: #f5f5f5;
/* ... */
}
}
```
Values can be any CSS colour syntax — hex, `oklch()`, `hsl()`, `color-mix()`. Hex is used here only because it is the easiest to paste from a design tool.
## Keeping it readable
Nothing stops you shipping unreadable colours, so two checks are worth doing by hand — they are the ones every shipped palette is held to.
- **Text on its own surface.** Each `-foreground` against its partner should reach 4.5:1 — `--foreground` on `--background`, `--primary-foreground` on `--primary`, and so on down the list.
- **The destructive fill.** Destructive buttons print white text on `--destructive` regardless of your tokens, so that colour has to be dark enough for white to read on it — and still distinct enough from `--background` to look like a control.
Any contrast checker will do. The failure to watch for is a `--primary` that looks great as a large button and becomes unreadable as a small link.
## Corner radius
`--radius` is one length that every component derives its corners from. Set it to `0rem` for a square, technical look, or past `1rem` for a soft, consumer one — nothing else needs to change.
```css title="app/globals.css"
:root {
--radius: 0rem; /* square */
--radius: 1.25rem; /* or round */
}
```
## Changing only part of the app
Tokens obey the cascade like any other CSS property, so an override can be attached to any selector rather than the whole document. Useful for a marketing page that should look louder than the product, or an embedded widget that has to match a host.
```css title="app/globals.css"
/* An override does not have to be global. Any selector works, because
tokens cascade like any other CSS property. */
.marketing-site {
--primary: #db2777;
--radius: 1.5rem;
}
/* Everything inside is pink and round; the rest of the app is untouched. */
```
This is exactly how the shipped palettes work in their `/scoped` form — they attach to `[data-palette="name"]` instead of `:root`. See the [Themes](https://ui.xiod.dev/docs/styling/themes) page.
## Changing a single element
When you want one button to be different rather than every button, do not touch the tokens at all.
```tsx title="Any component"
/* For a single element, skip tokens entirely — every component
forwards className, and your class wins. */
/* Reach for a token when you want the change everywhere,
and for className when you want it exactly here. */
```
---
# Icons
> Swap any built-in icon, per instance or app-wide.
Source: https://ui.xiod.dev/docs/styling/icons
## Where the icons come from
Components draw their icons from [XiodIcons](https://icons.xiod.dev), which installs with `xiod-ui`. Every one of them can be replaced: with a component from another icon library, an inline `