Nuxt UI: look and theme reference for a non-Vue component kit

Source snapshot: github.com/nuxt/ui, branch v4 default, commit 9c4e33b7e076aa6bd59b5bd99d8621d9dc1bcf60 (2026-10-08), package.json version 4.11.3. It is built on Tailwind CSS v4, Reka UI and tailwind-variants. Every value below comes from that tree unless a URL says otherwise. Docs live at https://ui.nuxt.com/docs.

Notation used throughout:

  • Tailwind v4 spacing unit is --spacing: 0.25rem, so p-2.5 is 10px and size-5 is 20px (at 16px root).
  • text-xs is 12px with 16px line height, text-sm is 14/20, text-base is 16/24, text-lg is 18/28, text-xl is 20/28 and text-2xl is 24/32. text-sm/4 means 14px with a 16px line, and text-[10px]/3 means 10px with a 12px line.
  • font-medium is 500 and font-semibold is 600.
  • ring is a 1px inset box-shadow (ring-inset). outline-3 is a 3px solid outline at offset 0. X/25 means colour X at alpha 0.25 (Tailwind uses color-mix(in oklab, X 25%, transparent)).
  • Semantic utility names map to CSS variables. text-default is --ui-text, text-muted is --ui-text-muted, bg-default is --ui-bg, bg-elevated is --ui-bg-elevated, ring-accented is --ui-border-accented, bg-inverted is --ui-bg-inverted, bg-border is --ui-border, and so on. See section 2.4.
  • bg-primary, text-primary and similar use --ui-primary.

1. Licence

Source: LICENSE.md at the repo root (https://github.com/nuxt/ui/blob/v4/LICENSE.md).

MIT License

Copyright (c) 2023 Nuxt

Keep the full MIT permission and warranty text with that copyright line in the third-party notice. Two related notices:

  • The default icon set is Lucide (i-lucide-*, src/theme/icons.ts), which is ISC-licensed.
  • The palettes come from Tailwind CSS (MIT, Copyright (c) Tailwind Labs, Inc.).

Copying palette values or icons brings in those notices as well.


2. Design tokens

2.1 Colour aliases and default palettes

Sources: src/utils/defaults.ts (getDefaultConfig, resolveColors) and the docs page https://ui.nuxt.com/docs/getting-started/theme/design-system.

AliasDefault Tailwind palette
primarygreen
secondaryblue
successgreen
infoblue
warningyellow
errorred
neutralslate
  • theme.colors defaults to ['primary','secondary','success','info','warning','error']. neutral is always present and handled separately: every component has a dedicated neutral branch that uses the semantic tokens instead of a palette.
  • Users override the mapping through app.config.ts (ui.colors.primary = 'indigo').
  • If neutral is set to 'neutral', Nuxt UI substitutes the Tailwind v3 hex neutral palette (--color-old-neutral-*) in src/templates.ts and runtime/plugins/colors.ts. This does not apply to the default (slate).

Generated alias variables (src/runtime/plugins/colors.ts):

--ui-color-<alias>-<50..950> = the palette's shade
:root, .light { --ui-<alias>: var(--ui-color-<alias>-500); }   /* light: shade 500 */
.dark         { --ui-<alias>: var(--ui-color-<alias>-400); }   /* dark:  shade 400 */

This rule covers every alias except neutral. For example, --ui-primary is green-500 in light mode and green-400 in dark mode.

2.2 Tailwind v4 palettes used by default

Source: tailwindcss@4.1.11/theme.css. Tailwind defines them in OKLCH, and the hex values below are the sRGB conversions (they match Tailwind’s published hex).

50100200300400500600700800900950
slate#f8fafc#f1f5f9#e2e8f0#cad5e2#90a1b9#62748e#45556c#314158#1d293d#0f172b#020618
green#f0fdf4#dcfce7#b9f8cf#7bf1a8#05df72#00c950#00a63e#008236#016630#0d542b#032e15
blue#eff6ff#dbeafe#bedbff#8ec5ff#51a2ff#2b7fff#155dfc#1447e6#193cb8#1c398e#162456
yellow#fefce8#fef9c2#fff085#ffdf20#fdc700#f0b100#d08700#a65f00#894b00#733e0a#432004
red#fef2f2#ffe2e2#ffc9c9#ffa2a2#ff6467#fb2c36#e7000b#c10007#9f0712#82181a#460809

Exact OKLCH sources for the shades used most:

slate-50 oklch(98.4% 0.003 247.858); slate-100 oklch(96.8% 0.007 247.896); slate-200 oklch(92.9% 0.013 255.508); slate-300 oklch(86.9% 0.022 252.894); slate-400 oklch(70.4% 0.04 256.788); slate-500 oklch(55.4% 0.046 257.417); slate-600 oklch(44.6% 0.043 257.281); slate-700 oklch(37.2% 0.044 257.287); slate-800 oklch(27.9% 0.041 260.031); slate-900 oklch(20.8% 0.042 265.755); green-400 oklch(79.2% 0.209 151.711); green-500 oklch(72.3% 0.219 149.579); blue-400 oklch(70.7% 0.165 254.624); blue-500 oklch(62.3% 0.214 259.815); yellow-400 oklch(85.2% 0.199 91.936); yellow-500 oklch(79.5% 0.184 86.047); red-400 oklch(70.4% 0.191 22.216); red-500 oklch(63.7% 0.237 25.331).

2.3 Semantic CSS variables (light and dark)

Source: src/runtime/index.css (@layer theme). Light values apply on :root, :host, .light, dark values on .dark. Here “neutral” means --ui-color-neutral-*, which is slate by default.

VariableLightDarkLight hexDark hex
--ui-primary (and every alias)primary-500primary-400#00c950#05df72
--ui-text-dimmedneutral-400neutral-500#90a1b9#62748e
--ui-text-mutedneutral-500neutral-400#62748e#90a1b9
--ui-text-tonedneutral-600neutral-300#45556c#cad5e2
--ui-textneutral-700neutral-200#314158#e2e8f0
--ui-text-highlightedneutral-900white#0f172b#ffffff
--ui-text-invertedwhiteneutral-900#ffffff#0f172b
--ui-bgwhiteneutral-900#ffffff#0f172b
--ui-bg-mutedneutral-50neutral-800#f8fafc#1d293d
--ui-bg-elevatedneutral-100neutral-800#f1f5f9#1d293d
--ui-bg-accentedneutral-200neutral-700#e2e8f0#314158
--ui-bg-invertedneutral-900white#0f172b#ffffff
--ui-borderneutral-200neutral-800#e2e8f0#1d293d
--ui-border-mutedneutral-200neutral-700#e2e8f0#314158
--ui-border-accentedneutral-300neutral-700#cad5e2#314158
--ui-border-invertedneutral-900white#0f172b#ffffff

Mode-independent variables (:root, :host):

VariableValue
--ui-radius0.25rem (4px)
--ui-container80rem (1280px). UContainer max width.
--ui-header-height4rem (64px). Used by Modal and Slideover headers, DashboardNavbar and DashboardSidebar header.

The body base style is antialiased text-default bg-default scheme-light dark:scheme-dark (src/templates.ts, the ui.css template).

2.4 Tailwind theme bridge and radius scale

Source: src/templates.ts, the @theme default inline block.

--radius-xs:  calc(var(--ui-radius) * 0.5)   = 2px
--radius-sm:  var(--ui-radius)               = 4px
--radius-md:  calc(var(--ui-radius) * 1.5)   = 6px
--radius-lg:  calc(var(--ui-radius) * 2)     = 8px
--radius-xl:  calc(var(--ui-radius) * 3)     = 12px
--radius-2xl: calc(var(--ui-radius) * 4)     = 16px
--radius-3xl: calc(var(--ui-radius) * 6)     = 24px
(rounded-full = 9999px, unchanged)

The pixel values assume the default --ui-radius of 4px.

Utility to variable mapping (same block):

UtilityMaps to
text-{dimmed,muted,toned,default,highlighted,inverted}the matching --ui-text*
bg-{default,muted,elevated,accented,inverted}the matching --ui-bg*
bg-border--ui-border
border-, ring-, divide-, ring-offset- + {default,muted,accented,inverted}the matching --ui-border*
border-bg, ring-bg, divide-bg, ring-offset-bg--ui-bg
outline-default--ui-border
outline-inverted--ui-border-inverted
stroke- / fill- + {bg,default,inverted}--ui-bg, --ui-border and --ui-border-inverted respectively
--color-<alias>var(--ui-<alias>)

2.5 Motion and shadow tokens

Sources: Tailwind v4 theme.css and src/runtime/keyframes.css.

Easing curves

TokenValue
--ease-outcubic-bezier(0, 0, 0.2, 1) (almost every Nuxt UI animation)
--ease-incubic-bezier(0.4, 0, 1, 1)
--ease-in-outcubic-bezier(0.4, 0, 0.2, 1)

Shadows

TokenValue
shadow-xs0 1px 2px 0 rgb(0 0 0 / 0.05)
shadow-sm0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1)
shadow-lg0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1)

Keyframes

KeyframeAnimation
scale-inopacity 0 to 1 and scale(0.95) to scale(1)
scale-outthe reverse of scale-in
fade-in / fade-outopacity 0 to 1 / 1 to 0
slide-in-from-{top,right,bottom,left}translate ±100% to 0
slide-out-to-{top,right,bottom,left}translate 0 to ±100%
accordion-down / accordion-upheight 0 to/from --reka-accordion-content-height, overflow hidden
collapsible-down / collapsible-upsame as accordion, using the collapsible height variable
toast-*toast slide, pulse and swipe animations

Under prefers-reduced-motion, the scale and slide keyframes are redefined as opacity-only fades (keyframes.css around line 620).

Transitions

When theme.transitions is true (the default), interactive elements add transition-colors. Tailwind’s default for that is 150ms with cubic-bezier(0.4, 0, 0.2, 1).

Common durations

DurationUsed for
100mspopovers, menus and tooltips
200msmodal, slideover, accordion, toast, tab indicator and switch thumb

2.6 Shared focus and disabled conventions

These apply across components unless a section says otherwise.

  • Focus ring. outline-<c>/25 focus-visible:outline-3 gives a 3px outline at 25% alpha of the colour. For outline and subtle styles, focus-visible:ring-<c> also turns the 1px inset ring fully opaque. With color=neutral, <c> is the inverted token.
  • Disabled state. disabled:opacity-75 disabled:cursor-not-allowed.
  • Field groups. When a component sits in a UFieldGroup, its inner corners are squared (rounded-e-none and similar) and the group uses -space-x-px (src/theme/field-group.ts).

3. Components

Each section cites src/theme/<file>.ts for classes and src/runtime/components/<Name>.vue for props. The class lists are verbatim. The <c> placeholder stands for any non-neutral alias such as primary.

3.1 Button

Sources: src/theme/button.ts and Button.vue. Docs: https://ui.nuxt.com/docs/components/button.

Props: label (string); color (alias or 'neutral', primary); variant (solid | outline | soft | subtle | ghost | link, solid); size (xs | sm | md | lg | xl, md); activeColor, activeVariant (used when a link is active); square (boolean, false. Set automatically when there is an icon and no label.); block (boolean, false); loading, loadingAuto (boolean, false); loadingIcon (icon, i-lucide-loader-circle); icon, leadingIcon, trailingIcon, avatar, leading, trailing; Link props (to, href, target, disabled).

Base classes: rounded-md font-medium inline-flex items-center disabled:opacity-75 transition-colors. The radius is 6px.

Sizes

SizePaddingTextGapIconAvatarSquare padding
xspx-2 py-1 (8/4px)text-xsgap-1 (4px)size-4 (16)3xsp-1
smpx-2.5 py-1.5 (10/6)text-xsgap-1.5size-43xsp-1.5
mdpx-2.5 py-1.5text-smgap-1.5size-5 (20)2xsp-1.5
lgpx-3 py-2 (12/8)text-smgap-2size-52xsp-2
xlpx-3 py-2text-basegap-2size-6 (24)xsp-2

block adds w-full justify-center, and the trailing icon gets ms-auto. Loading spins the leading icon (animate-spin), or the trailing icon when the button has only a trailing one.

Resulting heights with the default line heights:

SizeHeight
xs24px
sm28px
md32px
lg36px
xl40px

Variants, color=<c> (compoundVariants, verbatim):

VariantClasses
solidtext-inverted bg-<c> hover:bg-<c>/75 active:bg-<c>/75 disabled:bg-<c> outline-<c>/25 focus-visible:outline-3
outlinering ring-inset ring-<c>/50 text-<c> hover:bg-<c>/10 active:bg-<c>/10 disabled:bg-transparent outline-<c>/25 focus-visible:outline-3 focus-visible:ring-<c>
softtext-<c> bg-<c>/10 hover:bg-<c>/15 active:bg-<c>/15 outline-<c>/25 focus-visible:outline-3 disabled:bg-<c>/10
subtletext-<c> ring ring-inset ring-<c>/25 bg-<c>/10 hover:bg-<c>/15 active:bg-<c>/15 disabled:bg-<c>/10 outline-<c>/25 focus-visible:outline-3 focus-visible:ring-<c>
ghosttext-<c> hover:bg-<c>/10 active:bg-<c>/10 outline-<c>/25 focus-visible:outline-3 disabled:bg-transparent
linktext-<c> hover:text-<c>/75 active:text-<c>/75 disabled:text-<c> outline-<c>/25 focus-visible:outline-3

Variants, color=neutral:

VariantClasses
solidtext-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted outline-inverted/25 focus-visible:outline-3
outlinering ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted
softtext-default bg-elevated hover:bg-accented/75 active:bg-accented/75 outline-inverted/25 focus-visible:outline-3 disabled:bg-elevated
subtlering ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted
ghosttext-default hover:bg-elevated active:bg-elevated outline-inverted/25 focus-visible:outline-3 (no hover background while disabled)
linktext-muted hover:text-default active:text-default disabled:text-muted outline-inverted/25 focus-visible:outline-3

The source also lists aria-disabled: and dark:disabled: twins of each disabled: class. They are omitted above because they use identical values.

3.2 Badge

Sources: src/theme/badge.ts and Badge.vue.

Props: as ('span'); label (string or number); color (alias or neutral, primary); variant (solid | outline | soft | subtle (no ghost or link), solid); size (xs..xl, md); square (boolean, auto when there is no label); icon and avatar props (as Button).

Base classes: font-medium inline-flex items-center.

Sizes

SizeTextPaddingGapRadiusIconSquare
xstext-[8px]/3 (8px, 12px line)px-1 py-0.5 (4/2)gap-1rounded-sm (4px)size-3 (12)p-0.5
smtext-[10px]/3px-1.5 py-1 (6/4)gap-1rounded-smsize-3p-1
mdtext-xspx-2 py-1 (8/4)gap-1rounded-md (6px)size-4p-1
lgtext-smpx-2 py-1gap-1.5rounded-mdsize-5p-1
xltext-basepx-2.5 py-1gap-1.5rounded-mdsize-6p-1

Variants

Variantcolor=<c>color=neutral
solidbg-<c> text-invertedtext-inverted bg-inverted
outlinetext-<c> ring ring-inset ring-<c>/50ring ring-inset ring-accented text-default bg-default
softbg-<c>/10 text-<c>text-default bg-elevated
subtlebg-<c>/10 text-<c> ring ring-inset ring-<c>/25ring ring-inset ring-accented text-default bg-elevated

3.3 Avatar

Sources: src/theme/avatar.ts and Avatar.vue.

Props: src, alt (string); icon (icon); text (string. Otherwise initials come from alt.); size (3xs..3xl, md); color (alias or neutral, neutral); chip (boolean or ChipProps); as ('span').

Classes

SlotClasses
rootinline-flex items-center justify-center shrink-0 select-none rounded-full align-middle
imageh-full w-full rounded-[inherit] object-cover
fallbackfont-medium truncate
Colourrootfallback and icon
neutralbg-elevatedtext-muted
<c>bg-<c>/10text-<c>

Sizes (root box and font size):

SizeBoxFont
3xs16px8px
2xs20px10px
xs24px12px
sm28px14px
md32px16px
lg36px18px
xl40px20px
2xl44px22px
3xl48px24px

3.4 Card

Sources: src/theme/card.ts and Card.vue.

Props: as ('div'); title (string); description (string); variant (solid | outline | soft | subtle, outline).

Card has no sizes and no colour prop.

Slots

SlotClasses
rootrounded-lg overflow-hidden (8px)
headerp-4 sm:px-6 (16px, or 24px horizontal at ≥640px)
bodyp-4 sm:p-6
footerp-4 sm:px-6
titletext-highlighted font-semibold
descriptionmt-1 text-muted text-sm

Variants

VariantClasses
outlineroot bg-default ring ring-default divide-y divide-default
solidroot bg-inverted text-inverted, title text-inverted, description text-dimmed
softroot bg-elevated/50 divide-y divide-default
subtleroot bg-elevated/50 ring ring-default divide-y divide-default

3.5 Input

Sources: src/theme/input.ts and Input.vue.

Props: type ('text'); placeholder (string); color (alias or neutral, primary); variant (outline | soft | subtle | ghost | none, outline); size (xs..xl, md); highlight (boolean, forces the coloured ring); fixed (boolean, false, which enables the responsive text sizes below); autocomplete ('off'); autofocusDelay (number, 0); disabled, required, loading, icon, leadingIcon, trailingIcon, avatar, modelValue.

Classes

SlotClasses
basew-full rounded-md border-0 placeholder:text-dimmed disabled:opacity-75 transition-colors
leadingIcon, trailingIconshrink-0 text-dimmed
leading, trailingabsolutely positioned and vertically centred

Sizes

SizePaddingText (mobile, then md: ≥768px)GapIconIcon insetPadding next to an icon
xspx-2 py-1text-sm/4, then md:text-xsgap-1size-4ps-2/pe-2ps-7/pe-7 (28px)
smpx-2.5 py-1.5text-sm/4, then md:text-xsgap-1.5size-4ps-2.5ps-8 (32)
mdpx-2.5 py-1.5text-base/5, then md:text-smgap-1.5size-5ps-2.5ps-9 (36)
lgpx-3 py-2text-base/5, then md:text-smgap-2size-5ps-3ps-10 (40)
xlpx-3 py-2text-basegap-2size-6ps-3ps-11 (44)

The larger text on mobile avoids iOS zoom. On desktop the sizes match Button: md is 14px text in a 32px-high field.

Variants (base, any colour):

VariantClasses
outlinetext-highlighted bg-default ring ring-inset ring-accented
softtext-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50
subtletext-highlighted bg-elevated ring ring-inset ring-accented
ghosttext-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent
nonetext-highlighted bg-transparent focus:outline-none

Colour compounds

Applies to<c>neutral
outline, subtleoutline-<c>/25 focus-visible:outline-3 focus-visible:ring-<c>outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted
soft, ghostoutline-<c>/25 focus-visible:outline-3the inverted equivalent
highlightring ring-inset ring-<c>ring-inverted

3.6 Textarea

Sources: src/theme/textarea.ts (merges in input.ts) and Textarea.vue.

Props: the same as Input, plus: rows (3); maxrows (0); autoresize (false); autoresizeDelay (0).

Classes, variants, colours and sizes are identical to Input. The only differences:

  • Leading and trailing slots are items-start and pinned to the top: inset-y-1 for xs, inset-y-1.5 for sm and md, inset-y-2 for lg and xl.
  • autoresize adds resize-none.

3.7 Select

Sources: src/theme/select.ts (merges in input.ts) and Select.vue.

Props: items; placeholder; color, variant, size (primary / outline / md); trailingIcon (i-lucide-chevron-down); selectedIcon (i-lucide-check); content ({ side:'bottom', sideOffset:8, collisionPadding:8, position:'popper' }); arrow (false); portal (true); valueKey ('value'); labelKey ('label'); descriptionKey ('description'); multiple, highlight, fixed.

Trigger. Uses the Input sizes and variants with these changes:

  • base becomes relative group rounded-md inline-flex items-center.
  • outline adds hover:bg-elevated disabled:bg-default.
  • subtle adds hover:bg-accented/75 disabled:bg-elevated.
  • placeholder is truncate text-dimmed.

Popup slots

SlotClasses
contentmax-h-[min(15rem, available)] w-(trigger width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden, animated with scale-in / scale-out over 100ms (popper mode)
viewportdivide-y divide-default overflow-y-auto
groupp-1
itemrelative flex items-start text-default; highlighted state text-highlighted and before:bg-elevated/50 (the before sits at inset-px rounded-md)
itemLeadingIcontext-dimmed, becoming text-default when highlighted
labelfont-semibold text-highlighted
separator-mx-1 my-1 h-px bg-border
itemDescriptiontext-muted

Item sizes

SizeItemLabelIconEmpty
xsp-1 text-xs gap-1p-1 text-[10px]/316p-2 text-xs
smp-1.5 text-xs gap-1.5p-1.5 text-[10px]/316p-2.5
mdp-1.5 text-sm gap-1.5p-1.5 text-xs20p-2.5 text-sm
lgp-2 text-sm gap-2p-2 text-xs20p-3
xlp-2 text-base gap-2p-2 text-sm24p-3 text-base

3.8 Checkbox

Sources: src/theme/checkbox.ts and Checkbox.vue.

Props: label, description; color (primary); variant (list | card, default list); size (md); indicator (start | end | hidden, default start); highlight; icon (i-lucide-check); indeterminateIcon (i-lucide-minus); modelValue (boolean | 'indeterminate'); required, disabled.

Slots

SlotClasses
base (the box)rounded-sm ring ring-inset ring-accented overflow-hidden (4px radius, 1px border-accented)
indicator (checked fill)size-full text-inverted bg-<c>, or bg-inverted for neutral
labelblock font-medium text-default
descriptiontext-muted
wrapperms-2 with indicator start, me-2 with end

required adds a red * after the label (after:text-error). disabled gives the root opacity-75.

Sizes

SizeBoxCheck iconRow height (container)TextCard padding
xs12px10h-4text-xsp-2.5
sm1412h-4text-xsp-3
md1614h-5text-smp-3.5
lg1816h-5text-smp-4
xl2018h-6text-basep-4.5

Card variant

  • Root: border border-default rounded-lg. Hover (not checked, not disabled): bg-elevated/50 border-accented.
  • Checked with <c>: border-<c>/50 bg-<c>/10. Checked with neutral: border-inverted/50 bg-elevated.

Focus

  • list variant: on the box, outline-<c>/25 focus-visible:outline-3 focus-visible:ring-<c>.
  • card variant: on the card, has-focus-visible:outline-3 and border-<c>.

3.9 Switch

Sources: src/theme/switch.ts and Switch.vue.

Props: color (primary); size (md); loading; loadingIcon (i-lucide-loader-circle); checkedIcon, uncheckedIcon; label, description; highlight, required, disabled.

Slots

SlotClasses
base (track)inline-flex items-center rounded-full border-2 border-transparent focus-visible:outline-3; unchecked bg-accented; checked bg-<c> (neutral: bg-inverted); outline <c>/25; transition-[background] duration-200 ease-out
thumbrounded-full bg-default shadow-lg, transition-transform 200ms ease-out
iconinside the thumb, size-10/12; text-dimmed when unchecked, text-<c> when checked

Sizes

SizeTrack widthContainer heightThumbChecked translateText
xsw-7 (28)h-412px12pxtext-xs
smw-8 (32)h-41414text-xs
mdw-9 (36)h-51616text-sm
lgw-10 (40)h-51818text-sm
xlw-11 (44)h-62020text-base

The track height is the thumb plus the 2px transparent border on each side, so md is 36x20. The wrapper is ms-2.

3.10 RadioGroup

Sources: src/theme/radio-group.ts and RadioGroup.vue.

Props: items; legend; valueKey ('value'); labelKey ('label'); descriptionKey ('description'); size (md); variant (list | card | table, default list); color (primary); orientation (vertical); indicator (start); highlight, disabled, required, modelValue.

Slots

SlotClasses
base (circle)rounded-full ring ring-inset ring-accented
indicator (checked)bg-<c> (or bg-inverted) with a centred dot after:bg-default after:rounded-full
fieldsetflex gap-x-2; flex-col when vertical
legendmb-1 font-medium text-default

Sizes

SizeCircleDotContainer heightTextFieldset gap-yCard/table padding
xs124h-4text-xs2pxp-2.5
sm144h-4text-xs2pxp-3
md166h-5text-sm4pxp-3.5
lg186h-5text-sm4pxp-4
xl208h-6text-base6pxp-4.5

Card and table variants

  • card: like Checkbox’s card. Items are border border-default rounded-lg. Checked: border-<c>/50 bg-<c>/10 (neutral: border-inverted/50 bg-elevated).
  • table: the items are joined. Horizontal uses -space-x-px, first:rounded-s-lg and last:rounded-e-lg. Vertical uses -space-y-px, first:rounded-t-lg and last:rounded-b-lg.

3.11 Tabs

Sources: src/theme/tabs.ts and Tabs.vue.

Props: items; color (primary); variant (pill | link, default pill); size (md); orientation (horizontal); content (true); defaultValue ('0'); unmountOnHide (true); valueKey ('value'); labelKey ('label').

Slots

SlotClasses
rootflex items-center gap-2; horizontal adds flex-col
listrelative flex p-1
triggerrelative inline-flex items-center font-medium rounded-md; inactive text-muted, hover text-default; transition-colors; horizontal adds justify-center
indicatorabsolute, transition-[translate,width] duration-200 ease-out, sized and positioned from --reka-tabs-indicator-size and --reka-tabs-indicator-position
contentw-full rounded-md focus-visible:outline-3 outline-<c>/25

Trigger sizes

SizeTriggerIcon
xspx-2 py-1 text-xs gap-116
smpx-2.5 py-1.5 text-xs gap-1.516
mdpx-3 py-1.5 text-sm gap-1.520
lgpx-3 py-2 text-sm gap-220
xlpx-3 py-2 text-base gap-224

Trailing badges are sm.

pill variant

  • list: bg-elevated rounded-lg (8px).
  • trigger: grow.
  • indicator: rounded-md shadow-xs plus bg-<c> (neutral bg-inverted). Horizontal adds inset-y-1; vertical adds inset-x-1.
  • active trigger: text-inverted.

link variant

  • list: border-default. Horizontal adds border-b -mb-px; vertical adds border-s -ms-px.
  • indicator: rounded-full bg-<c>, 1px. Horizontal sits at -bottom-px h-px; vertical uses w-px.
  • active trigger: text-<c> (neutral text-highlighted).

3.12 Accordion

Sources: src/theme/accordion.ts and Accordion.vue.

Props: items; type ('single'); collapsible (true); unmountOnHide (true); trailingIcon (i-lucide-chevron-down); valueKey ('value'); labelKey ('label'); disabled.

Accordion has no size, colour or variant.

Slots

SlotClasses
itemborder-b border-default last:border-b-0
triggerflex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 rounded-md outline-primary/25 focus-visible:outline-3
leadingIcon, trailingIconsize-5; the trailing icon is ms-auto and rotates 180° when open (transition-transform 200ms ease-out)
contentaccordion-down / accordion-up over 200ms ease-out (height animation)
bodytext-sm pb-3.5

Disabled triggers get opacity-75.

3.13 Modal

Sources: src/theme/modal.ts and Modal.vue.

Props: title, description; overlay (true); scrollable (false); transition (true); fullscreen (false); portal (true); close (true. A ghost neutral Button with i-lucide-x.); dismissible (true); modal (true); open (v-model).

Slots

SlotClasses
overlayfixed inset-0 bg-elevated/75
content (default)bg-default divide-y divide-default flex flex-col w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default (max width 512px, 8px radius); fixed and centred (top-1/2 left-1/2 -translate-*) with max-h-[calc(100dvh-2rem)], or -4rem at ≥640px
content (fullscreen)inset-0
content (scrollable)the overlay scrolls and centres the content as a grid (p-4 sm:py-8)
headerflex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height) (64px)
bodyflex-1 p-4 sm:p-6 (overflow-y-auto when not scrollable)
footerflex items-center gap-1.5 p-4 sm:px-6
titletext-highlighted font-semibold
descriptionmt-1 text-muted text-sm
closeabsolute top-4 end-4

Transitions (200ms ease-out): the overlay fades in and out; the content uses scale-in / scale-out (0.95 to 1 with opacity).

3.14 Slideover

Sources: src/theme/slideover.ts and Slideover.vue.

Props: the same as Modal, minus scrollable and fullscreen, plus: side (top | right | bottom | left, default right); inset (false).

Slots

SlotClasses
overlayfixed inset-0 bg-elevated/75
contentfixed bg-default divide-y divide-default sm:ring ring-default sm:shadow-lg flex flex-col
bodyflex-1 overflow-y-auto p-4 sm:p-6

Header, footer, title, description and close are identical to Modal.

Placement

Sideinset = falseinset = true
left or rightw-full inset-y-0, plus sm:max-w-md (448px)w-[calc(100%-2rem)] inset-y-4 right-4/left-4 rounded-lg
top or bottommax-h-full inset-x-0max-h-[calc(100%-2rem)] inset-x-4 top-4/bottom-4 rounded-lg

Transition: slide-in-from-<side> / slide-out-to-<side> (translate ±100%), 200ms ease-out, with an overlay fade.

3.15 DropdownMenu

Sources: src/theme/dropdown-menu.ts and DropdownMenu.vue.

Props: items (nested arrays form groups; item types are label, separator, checkbox and link); size (md); content ({ side:'bottom', sideOffset:8, collisionPadding:8 }); arrow (false); portal (true); modal (true); checkedIcon (i-lucide-check); loadingIcon (spinner); externalIcon (true (i-lucide-arrow-up-right)); filter (false); labelKey ('label'); descriptionKey ('description'); disabled.

Each item can also carry a per-item color and kbds.

Slots

SlotClasses
contentmin-w-32 max-h-(available height) bg-default shadow-lg rounded-md ring ring-default overflow-hidden (128px min width), scale-in / scale-out 100ms, transform origin from Reka
viewportdivide-y divide-default overflow-y-auto
groupp-1
labelfont-semibold text-highlighted
separator-mx-1 my-1 h-px bg-border
itemrelative flex items-start select-none with highlight drawn as before:absolute before:inset-px before:rounded-md
item (not active)text-default, highlighted or open text-highlighted before:bg-elevated/50; leading icon text-dimmed, becoming text-default
item (active)text-highlighted before:bg-elevated
item with color=<c>text-<c>, highlighted before:bg-<c>/10; icon text-<c>/75, becoming text-<c>
itemTrailingKbdshidden lg:inline-flex
itemDescriptiontext-muted

Sizes

SizeItem and labelIconKbds gap / size
xsp-1 text-xs gap-1162px / sm
smp-1.5 text-xs gap-1.5162px / sm
mdp-1.5 text-sm gap-1.5202px / md
lgp-2 text-sm gap-2204px / md
xlp-2 text-base gap-2244px / lg

3.16 Tooltip

Sources: src/theme/tooltip.ts, Tooltip.vue and App.vue (TooltipProvider).

Props: text; kbds; content ({ side:'bottom', sideOffset:8, collisionPadding:8 }); arrow (false); portal (true); reference; delayDuration (Reka provider default 700ms (skipDelayDuration 300ms). Configurable through <UApp :tooltip>.).

Slots

SlotClasses
contentflex items-center gap-1 bg-default text-highlighted shadow-sm rounded-sm ring ring-default h-6 px-2.5 py-1 text-xs select-none (24px high, 4px radius, 12px text); scale-in on delayed open and scale-out on close, both 100ms
arrowfill-bg stroke-default
kbdshidden lg:inline-flex gap-0.5, separated by ·, kbd size sm

3.17 Toast and Toaster

Sources: src/theme/toast.ts, src/theme/toaster.ts, Toast.vue, Toaster.vue and the useToast() composable.

Toast props

Props: title, description; icon; avatar (avatarSize 2xl); color (primary); orientation (vertical); close (true. A link neutral Button, size="sm".); closeIcon (i-lucide-x); actions (Buttons rendered with size="xs" and the toast colour); duration (5000); progress (true. A bottom progress bar.).

Toast slots

SlotClasses
rootrelative overflow-hidden bg-default shadow-lg rounded-lg ring ring-default p-4 flex gap-2.5 (16px padding, 10px gap, 8px radius)
iconsize-5 text-<c> (neutral text-highlighted)
titletext-sm font-medium text-highlighted
descriptiontext-sm text-muted (mt-1 when there is a title)
actionsflex gap-1.5; vertical orientation adds mt-2.5 below the text
progressabsolute inset-x-0 bottom-0

Toaster props

Props: position (bottom-right. Six positions.); expand (true); duration (5000); progress (true); max (5); portal (true).

Toaster slots

  • viewport: fixed flex flex-col w-[calc(100%-2rem)] sm:w-96 z-[100] (384px wide), placed at bottom-4 right-4 and similar.
  • toast: stacked absolutely using --index, --transform and --front-height. Non-front toasts are collapsed and their content hidden. Slide-in comes from the top or bottom edge over 200ms, and swipe-to-dismiss follows the swipe direction.

3.18 Table

Sources: src/theme/table.ts and Table.vue. It is built on TanStack Table.

Props: data; columns (TanStack ColumnDef); caption (sr-only); sticky (boolean | 'header' | 'footer', default false); loading; loadingColor (primary); loadingAnimation (carousel | carousel-inverse | swing | elastic, default carousel); empty (t('table.noData')); virtualize (false (estimateSize 65, overscan 12)); onSelect, onHover; state props (sorting, selection, pinning and similar).

Slots

SlotClasses
rootrelative overflow-auto
basemin-w-full
tbodydivide-y divide-default; selectable rows hover:bg-elevated/50
tr (selected)bg-elevated/50
thpx-4 py-3.5 text-sm text-highlighted text-start font-semibold
tdp-4 text-sm text-muted whitespace-nowrap
separator (under thead)h-px bg-(--ui-border-accented)
emptypy-6 text-center text-sm text-muted
sticky thead / tfootbg-default/75 backdrop-blur-sm
pinned cellssticky bg-default/75
loadinga 1px bar under thead (after:bg-<c>) animated with carousel over 2s, linear

3.19 Pagination

Sources: src/theme/pagination.ts and Pagination.vue. It is composed entirely of Buttons.

Props: page (v-model); total (0); itemsPerPage (10); siblingCount (2); showEdges (false); showControls (true); color (neutral); variant (outline); activeColor (primary); activeVariant (solid); size (Button default md); to ((page) => link); firstIcon (i-lucide-chevrons-left); prevIcon (i-lucide-chevron-left); nextIcon (i-lucide-chevron-right); lastIcon (i-lucide-chevrons-right); ellipsisIcon (i-lucide-ellipsis).

Slots

SlotClasses
listflex items-center gap-1 (4px)
labelmin-w-5 text-center (page numbers at least 20px wide)
ellipsispointer-events-none; rendered as a non-interactive Button

The look therefore comes from Button: neutral outline (section 3.1) for every page, and primary solid for the current one.

3.20 Breadcrumb

Sources: src/theme/breadcrumb.ts and Breadcrumb.vue.

Props: items ({ label, icon, avatar, to, disabled }); separatorIcon (i-lucide-chevron-right); color (primary); labelKey ('label'); as ('nav').

Slots

SlotClasses
listflex items-center gap-1.5
linkrelative flex items-center gap-1.5 text-sm min-w-0 rounded-md
link (inactive)text-muted font-medium, with hover:text-default transition-colors when it has a to
link (active, last item)font-semibold text-<c> (neutral text-highlighted)
link (disabled)opacity-75
leading iconsize-5
avatar2xs
separatorIconsize-5 text-muted
focusoutline-<c>/25 focus-visible:outline-3

3.21 NavigationMenu

Sources: src/theme/navigation-menu.ts and NavigationMenu.vue.

Props: items (flat or nested arrays; children produce dropdown or collapsible content); orientation (horizontal); contentOrientation (horizontal); variant (pill | link, default pill); color (primary); highlight (false); highlightColor (primary); collapsed (false (vertical only)); tooltip (false); popover (false); arrow (false); type ('multiple'); trailingIcon (i-lucide-chevron-down); externalIcon (true); delayDuration (0); unmountOnHide (true); valueKey ('value'); labelKey ('label').

Slots

SlotClasses
linkrelative w-full flex items-center gap-1.5 font-medium text-sm; background drawn on before: with before:rounded-md
link iconsize-5
trailing badgesm
trailing chevronsize-5, rotating 180° when open
label (group heading)font-semibold text-xs/5 text-highlighted px-2.5 py-1.5

Orientation

OrientationRules
horizontalroot items-center justify-between; list flex; item py-2; link px-2.5 py-1.5 before:inset-x-px before:inset-y-0
verticalroot flex-col; link px-2.5 py-1.5 before:inset-y-px
vertical, childrenchildren nest under ms-5 border-s border-default with child items ps-1.5 and animate with collapsible-down / collapsible-up over 200ms
vertical, collapsedlink px-1.5; label and trailing hidden

States

Statepilllink
inactivetext-muted, leading icon text-dimmed; hover text-highlighted before:bg-elevated/50text-muted; hover text-highlighted
active <c>text-<c> and before:bg-elevated (no highlight)text-<c>
active neutraltext-highlightedtext-highlighted

Highlight: draws an after: bar in highlightColor.

  • horizontal: after:-bottom-2 after:inset-x-2.5 after:h-px.
  • vertical, level: after:-start-1.5 after:w-px.

Horizontal dropdown popup

  • viewport: bg-default shadow-lg rounded-md ring ring-default, scale-in over 100ms, animating width and height over 200ms.
  • child list: grid grid-cols-2 gap-2 p-2.
  • childLink: px-3 py-2 gap-2 text-sm, hover before:bg-elevated/50, active before:bg-elevated text-highlighted.
  • childLinkDescription: text-muted.

3.22 Dashboard layout

Sources: src/theme/dashboard-*.ts, Dashboard*.vue and src/runtime/composables/useResizable.ts. Docs: https://ui.nuxt.com/docs/components/dashboard-group.

DashboardGroup

  • Props: storage 'cookie' | 'local' (default 'cookie'), storageKey 'dashboard', persistent true, unit '%' | 'rem' | 'px' (default '%').
  • Base: fixed inset-0 flex overflow-hidden.
  • Provides a context with sidebarOpen, sidebarCollapsed and the toggle and collapse functions.

DashboardSidebar

Props: id; side ('left'); mode ('slideover' | 'modal' | 'drawer', default 'slideover'. This is the mobile presentation.); menu (props passed through to that overlay); toggle (true); toggleSide ('left'); autoClose (true); resizable (false); collapsible (false); minSize (10); maxSize (20); defaultSize (15); collapsedSize (0).

minSize, maxSize and defaultSize are in unit (% by default). open and collapsed are v-models.

Slots:

SlotClasses
rootrelative hidden lg:flex flex-col min-h-svh min-w-16 w-(--width) shrink-0; side=left adds border-e border-default
headerh-(--ui-header-height) shrink-0 flex items-center gap-1.5 px-4 (64px)
bodyflex flex-col gap-4 flex-1 overflow-y-auto px-4 py-2
footershrink-0 flex items-center gap-1.5 px-4 py-2

Behaviour:

  • Hidden below lg (1024px). There, the same content renders inside a Slideover, Modal or Drawer opened by DashboardSidebarToggle (lg:hidden, a Button), and the menu variant adds sm:px-6 paddings.
  • DashboardSidebarCollapse (hidden lg:flex) collapses it.
  • The resize handle (DashboardResizeHandle) is cursor-ew-resize with a 12px hit zone (before:-left-1.5 before:-right-1.5).

DashboardPanel

  • Props: id, minSize 15, maxSize, defaultSize, resizable false.
  • root: relative flex flex-col min-w-0 min-h-svh lg:not-last:border-e lg:not-last:border-default shrink-0. With a size it becomes w-full lg:w-(--width); otherwise flex-1.
  • body: flex flex-col gap-4 sm:gap-6 flex-1 overflow-y-auto p-4 sm:p-6.
  • The header and footer slots usually hold DashboardNavbar and DashboardToolbar.

DashboardNavbar

Props: title, icon, toggle (default true; a sidebar toggle shown below lg) and toggleSide ('left').

SlotClasses
rooth-(--ui-header-height) shrink-0 flex items-center justify-between border-b border-default px-4 sm:px-6 gap-1.5 (64px high)
leftflex items-center gap-1.5 min-w-0
iconsize-5 me-1.5
titlefont-semibold text-highlighted truncate
centerhidden lg:flex
rightflex items-center gap-1.5

DashboardToolbar (often used with the navbar)

  • root: shrink-0 flex items-center justify-between border-b border-default px-4 sm:px-6 gap-1.5 overflow-x-auto min-h-[49px].

4. Fonts and typography

Sources: src/module.ts, src/templates.ts, Tailwind v4 theme.css and docs/app/assets/css/main.css.

Font family

The library sets no font family of its own. Text uses Tailwind’s --font-sans:

ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'

The mono stack is ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace. It is used by Kbd and code.

Web font loading

The module enables @nuxt/fonts (option fonts: true) with default weights [400, 500, 600, 700]. Any family a user names in --font-sans is then auto-downloaded.

The ui.nuxt.com site itself uses --font-sans: 'Public Sans', sans-serif (docs/app/assets/css/main.css). This is the face visible in the docs screenshots, so it is the one to use to match the docs look. Public Sans is SIL OFL 1.1.

Base styles

  • body: antialiased text-default bg-default. Default text is neutral-700 on white in light mode and neutral-200 on neutral-900 in dark mode.
  • Root font size: browser default, 16px.

Sizes used by the components (md defaults)

ElementSize and weight
Body text14px (text-sm, 20px line) for buttons, menu items, tabs, breadcrumb, nav links, table cells, accordion triggers, toast text and modal descriptions
Input14px on ≥768px, 16px on mobile
Small text12px for badge md, tooltip, menu group labels and xs/sm controls
Weightslabels and buttons 500 (font-medium); titles and headers (card, modal, table th, navbar, menu labels) 600 (font-semibold)

Colour hierarchy

RoleToken
Titlestext-highlighted
Bodytext-default
Secondary and descriptionstext-muted
Placeholders and inactive iconstext-dimmed

5. Behaviours that are hard for a non-DOM toolkit

These come from Reka UI primitives (https://reka-ui.com) driven by the components above.

ComponentHard parts
ButtonLink mode (router to, active-state matching); loadingAuto spins while an async @click promise is pending.
Input / TextareaText editing, IME, selection and caret; Textarea autoresize measures scrollHeight (with maxrows); responsive font size (md breakpoint).
SelectPortalled popup; floating positioning (side, offset 8, collision padding 8, flip and shift, width = trigger width, max height from available space); typeahead; roving highlight; scroll-into-view; focus return to the trigger; item-aligned mode overlays the selected item onto the trigger.
Checkbox / RadioGroup / SwitchMostly easy. Arrow-key roving focus inside RadioGroup; tri-state checkbox (indeterminate); sr-only native input for form semantics.
TabsMeasured animated indicator (needs each trigger’s size and position: --reka-tabs-indicator-size and --reka-tabs-indicator-position); arrow-key roving focus; unmountOnHide.
AccordionHeight animation to measured content height (--reka-accordion-content-height); single or multiple, collapsible.
Modal / SlideoverTeleport to body (portal); focus trap and initial or restore focus; scroll lock on the body; outside click and Escape dismiss (dismissible); stacked z-order; enter/exit animations that keep the node mounted until the animation ends (Presence); dvh sizing.
DropdownMenuPortal; floating positioning with collision handling and transform-origin; nested submenus with pointer-grace triangle; typeahead; checkbox items; keyboard shortcuts (kbds, defineShortcuts); modal mode blocks outside pointer events; optional filter input.
TooltipPortal; floating positioning; delay and skip-delay timers through a shared provider; hover and focus intent; dismiss on Escape or scroll.
ToastGlobal queue (useToast), max 5; stacked-collapsed layout that expands on hover (--index, --front-height, --transform computed from measured heights); swipe-to-dismiss with pointer tracking; auto-dismiss timer paused on hover or focus; progress bar tied to the timer; live-region announcements; F8 hotkey focus.
TableTanStack state (sorting, selection, pinning, expansion, grouping); sticky header and footer with backdrop blur; sticky pinned columns; virtualisation (TanStack Virtual); animated 1px loading bar.
Pagination / BreadcrumbTrivial apart from router links.
NavigationMenuHorizontal: hover-triggered portalled viewport that animates its size between items (--reka-navigation-menu-viewport-*) with directional content slide (data-motion) and a moving indicator arrow. Vertical: collapsible height animation; collapsed mode switches children to tooltip or popover.
Dashboard*Responsive switch at lg (desktop sidebar, or Slideover/Modal/Drawer on mobile); drag-to-resize panels with persisted sizes (cookie or localStorage, unit %/rem/px); collapse to collapsedSize; fixed full-viewport layout with independently scrolling panel bodies; keyboard shortcut to toggle.

Cross-cutting constructs to emulate:

  • before: / after: pseudo-element highlight layers. Menu and nav highlights are a rounded rectangle inset 1px behind the content.
  • Alpha-mixed colours (/10, /25, /50, /75). Blend in OKLab when matching closely; sRGB alpha is close enough in practice.
  • Focus-visible versus focus. Outlines show only for keyboard focus.
  • Reduced motion. Swap the scale and slide animations for opacity fades.
  • RTL. Logical properties (ms-, ps-, start-) and rtl: translate flips on Switch and Tabs.

Edit this page on GitHub ↗