npm

Customization

Swapping the defaults

VUI ships opinionated defaults so you can start without deciding anything. Every one of them is replaceable, and most swaps are a single line of CSS because the design system is tokens first. This page says exactly what is swappable, what it costs, and what is not.

PieceDefaultSwap toCost
IconsRadix Icons in the React componentsAny set: Lucide, Heroicons, Phosphor, your own SVGsOne line
CSS engineTailwind v4 utilities plus CSS-variable tokensA compiled stylesheet with no build (plain HTML, Laravel, Rails), or your own framework reading the tokensOne line
FontsInter, with a type scaleAny family and scale, per app or per userOne line
ChartsRecharts in React, TanStack Charts everywhereAny library that can read CSS variablesSome work
MotionDurations and easings as tokensRetune, or switch animation off entirelyOne line
Shell layoutInset sidebar, default density, left to rightFloating or flush sidebar, compact or full density, right to leftOne line
ComponentsReact and Vue, styled with Tailwind classesBootstrap or another component frameworkNot supported

How do I use a different icon set?

Add .vui-iconto your icons and they get the same treatment VUI's own icons get: a bordered chip, sized and coloured from the tokens. Nothing else to configure, in any framework.

The React components use Radix Icons internally, which render width="15", and the stylesheet targets that attribute so they are styled automatically. Your icons are not Radix, so they opt in with the class instead.

import { Star } from "lucide-react";

<Star className="vui-icon size-4" />

Turning the chip off

The bordered chip is a deliberate look, not a requirement. Two tokens remove it, for one icon or for the whole app:

globals.css
/* Everywhere */
:root {
  --vui-icon-chip-border: none;
  --vui-icon-chip-padding: 0;
}

/* Or just one icon: <Star className="vui-icon-plain size-4" /> */

Do I have to use Tailwind?

For the tokens, no. For the shipped components, yes.

The honest split: design tokens are 129 plain CSS variables that any framework can read, including Bootstrap. The components are styled with Tailwind utility classes, so they need those classes to exist. That is what the compiled stylesheet is for: it contains every utility VUI uses, already generated, so markup copied from these docs renders with no build step at all.

/* app/globals.css */
@import "tailwindcss";
@import "@viliha/vui-ui/theme.css";

Using the tokens with Bootstrap or another framework

Keep your framework and take VUI's palette. Map our variables onto theirs once and their components follow your theme, including dark mode, because the values change and the names do not.

bridge.css
@import "@viliha/vui-theme/theme.css";

:root {
  --bs-body-bg: var(--background);
  --bs-body-color: var(--foreground);
  --bs-primary: var(--button-primary);
  --bs-border-color: var(--border);
  --bs-border-radius: var(--radius);
}

What this does not give you

A Bootstrap button styled with VUI colours is a Bootstrap button. It will not match the shipped components pixel for pixel, and we do not maintain that bridge. Take the tokens for consistency across an app you are migrating; do not expect the two component sets to be interchangeable.

How do I change the font?

Set --font-sans. --font-scale multiplies the whole type scale, so you can make an app denser or roomier without touching a single component.

globals.css
:root {
  --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
  --font-scale: 1.05; /* everything 5% larger */
}

In an app that lets people choose, use the theming engine rather than hand-written CSS. THEME_FIELDS in @viliha/vui-core lists every themeable value, fonts included, and applyTheme writes them as variables on any element.

any framework
import { applyTheme, mergeThemes, FONT_FAMILIES } from "@viliha/vui-core";

applyTheme(document.documentElement, mergeThemes(orgTheme, { fontSans: "geist" }));

Can I use a different chart library?

Yes, and two are supported out of the box. Recharts is the default in React through ChartContainer. TanStack Charts works in React, Vue, Svelte, Solid and Angular from one definition, which is how non-React frameworks get charts at all. Both are covered on the charts page.

For anything else, the contract is small: read the chart tokens and inherit currentColor. Any library that accepts a colour string can do it.

bring your own
// Chart.js, ECharts, uPlot, D3 — read the same tokens the rest of the theme uses
const styles = getComputedStyle(document.documentElement);
const palette = [1, 2, 3, 4, 5].map((i) => styles.getPropertyValue(`--chart-${i}`));

// Or in CSS, for a library that paints with currentColor:
// .my-chart { color: var(--foreground); }

Why this stays consistent

Charts are the usual place a design system leaks, because chart colours get hard-coded per chart. Reading the tokens means a tenant's brand and dark mode reach the charts without anyone remembering to update them.

How do I change or disable animations?

Motion is tokens too. Retune the feel, or switch it off in one line.

globals.css
:root {
  --vui-duration-fast: 0.14s;  /* menus, popovers */
  --vui-duration-base: 0.2s;   /* overlays, toasts */
  --vui-duration-slow: 0.3s;   /* panels, slide-overs */
  --vui-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --vui-ease-panel: cubic-bezier(0.32, 0.72, 0, 1);
}

/* Off entirely */
:root { --vui-duration-fast: 0ms; --vui-duration-base: 0ms; --vui-duration-slow: 0ms; }

This is a product choice, separate from accessibility. VUI already honours prefers-reduced-motion and clamps every animation for people who ask their system for less motion, whatever these tokens say.

Can I move the sidebar, tighten the spacing, or run right to left?

Yes, and none of it needs a rebuild. Three attributes on the root element drive the whole shell, so a host can set them from a user preference, from the server to avoid a flash, or from a settings screen like the demo does at Settings → Layout.

index.html or your root layout
<html
  data-sidebar="inset"     <!-- inset | floating | plain -->
  data-density="default"   <!-- default | compact | full -->
  dir="ltr"                <!-- ltr | rtl -->
>

They set --vui-shell-gap, --vui-shell-radius, --vui-page-paddingand the sidebar's radius and shadow. Because it is all CSS, switching layout does not re-render anything.

Writing components that survive RTL

Most of it is free: flexbox follows the reading direction, so rows reverse by themselves. What does not flip is a physical utility. Use ms-, me-, ps-, pe-, start- and end- instead of ml-, mr-, pl-, pr-, left- and right-, and your component works in both directions with no rtl: variants at all.

What is not swappable?

  • The component styling engine. Buttons, dialogs, tables and the rest are Tailwind class strings. Swapping in Bootstrap classes would mean rewriting every component, so we do not claim it works.
  • The headless layer, per framework. React components use Radix, Vue uses Reka UI. Both are Radix-shaped, which is why the two look identical, but you cannot mix one into the other.
  • Tokens as the source of colour. You can change every token; you should not bypass them with a hard-coded hex. That is the one rule that keeps dark mode and per-tenant branding working.