@vuecs/list-controls
vuecs
The Vue 3 theming framework.
Components, themes, design tokens, dark mode, runtime palettes & locale —
all reskinnable through a single app.use() call.
Documentation · Getting Started · Components · Example Apps
Why vuecs?
Most component libraries lock you into one design system. vuecs inverts that: components ship undesigned — structure, behavior, and accessibility, with no design opinion baked in — and themes supply the design. A layered resolution engine wires the two together at runtime.
app.use(vuecs, { themes: [tailwind()] }); // an entire app, Tailwind-skinned
app.use(vuecs, { themes: [bootstrap()] }); // same app, now Bootstrap — no component changes
That inversion serves two audiences:
- App teams get one component set whose look comes entirely from the installed theme — the Bootstrap theme supplies real
.btn/.cardclasses, the Tailwind theme supplies utilities — so the components blend into an existing app without visual seams, and switching design systems later is a config swap, not a rewrite. - Component-library authors get a theming contract: build your own components on the same machinery, ship them with your default styling, and your consumers restyle them — together with vuecs and every other library on the contract — through one theme config. You stop dictating a CSS framework to everyone who installs your package.
The value proposition is layered, and every layer is independently consumable:
- 🎛️ Theming machinery (
@vuecs/core) —useComponentTheme/useComponentDefaultscomposables,<VCConfigProvider>,defineTheme()for composing themes on top of existing ones, a structured variant system (variant×color×size×compoundVariants), and typed, augmentable registries (ThemeElements,ComponentDefaults,Config). - 🧱 A full baseline of theme-aware components — buttons, forms, tables, lists, overlays, navigation, pagination, skeletons and more, built on that machinery with Reka UI primitives handling the accessibility heavy lifting.
- 🔌 An opt-in path for third-party libraries to publish components on the same machinery — so one
app.use(vuecs, { ... })call reskins everything: vuecs primitives and every library that builds on them.
Plus the runtime extras most design systems make you assemble yourself:
- 🌗 Dark mode — token-driven
.darkflips viauseColorMode()(light/dark/system), SSR-safe in Nuxt. - 🎨 Runtime palette switching —
useColorPalette().set({ primary: 'emerald' })re-tints every component and native Bootstrap/Bulma widgets, live, with no rebuild. - 🌍 Locale plumbing — browser-detected, cookie/storage-persisted locale source that bridges into every locale-aware component and composes with
vue-i18n/ any i18n library. - 🧩 Design tokens (
@vuecs/design) — OKLCH color scales, radius + motion primitives, consumable with Tailwind, Bootstrap, Bulma, or no framework at all.
Getting Started
npm install @vuecs/core @vuecs/button @vuecs/theme-tailwind @vuecs/design @vuecs/icon @vuecs/icons-lucide
import { createApp } from 'vue';
import vuecs, { extend } from '@vuecs/core';
import button from '@vuecs/button';
import tailwind from '@vuecs/theme-tailwind';
import lucide from '@vuecs/icons-lucide';
const app = createApp(App);
app.use(vuecs, {
themes: [tailwind()], // CSS class strings for every component
icons: [lucide()], // Iconify names for every semantic icon slot
overrides: {
elements: {
button: { classes: { root: extend('shadow-lg') } }, // merge on top of the theme
},
},
});
app.use(button);
<VCButton color="primary" variant="solid" icon-left="lucide:plus" :loading="busy">
Create
</VCButton>
Follow the full walkthrough at vuecs.dev/getting-started.
How theming works
Every component resolves its CSS classes through four layers, then applies structured variants on top:
┌─────────────────────────────────────────────┐
│ 4. instance prop :theme-class │ per-component override
│ 3. overrides app.use(vuecs, {...}) │ app-wide override
│ 2. themes tailwind(), custom() │ merged in array order
│ 1. component defaults vc-* structural │ always preserved
└─────────────────────────────────────────────┘
↓ then variants resolve
:theme-variant="{ color: 'error', size: 'sm' }"
Higher layers replace by default; wrap a value in extend() to merge instead. Themes are pure data — functions returning { elements, classesMergeFn? } — so they compose, stack, and ship from third-party packages. defineTheme({ extends: tailwindTheme(), elements: { ... } }) lets you publish a self-contained brand theme that inherits a shipped one.
→ Deep dive: Theme system · Variants · Composing themes · Build a themable component
Runtime superpowers
import { useColorMode, useColorPalette } from '@vuecs/design';
const { mode, resolved, toggle } = useColorMode(); // 'light' | 'dark' | 'system', persisted
const { set } = useColorPalette(); // re-tint the whole app at runtime
set({ primary: 'emerald', info: 'cyan' }); // no rebuild, persists across reloads
import { useLocaleManager } from '@vuecs/locale';
const { set, reset, resolved } = useLocaleManager();
set('de-DE'); // apply a backend-saved preference
reset(); // back to 'auto' → browser language
In Nuxt, all three (color mode, palette, locale) ship as SSR-safe, cookie-backed plugins via @vuecs/nuxt — rendered into the head before first paint, so there's no flash of the wrong theme.
Packages
Foundation
| Package | Version | Description |
|---|---|---|
@vuecs/core |
Theme resolution engine, variants, behavioral defaults, cross-cutting config, headless composables, VCPrimitive |
|
@vuecs/design |
CSS design tokens (OKLCH), dark mode, motion primitives, runtime palette engine | |
@vuecs/icon |
<VCIcon> — Iconify-backed icon component |
|
@vuecs/locale |
Browser-language-aware, resettable locale source |
Components
| Package | Version | Description |
|---|---|---|
@vuecs/button |
Button with variant × color × size axes, loading state, icon slots |
|
@vuecs/elements |
Atomic elements — Badge, Tag, Avatar, Separator, plus the Card / Alert / Collapse compounds | |
@vuecs/forms |
Complete form-input family on Reka primitives — input, select, slider, pin, tags, … | |
@vuecs/table |
Compound table — columns/data driver, multi-sort, selection, expandable rows | |
@vuecs/list |
Compound list with defineList() state factory and ARIA-listbox selection |
|
@vuecs/overlays |
Modal, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Toast | |
@vuecs/navigation |
Multi-level navigation with reactive registry, plus <VCStepper> |
|
@vuecs/pagination |
Offset/limit pagination on Reka's Pagination primitives | |
@vuecs/placeholder |
Skeleton loading primitives — wave/glow shimmer, reduced-motion aware | |
@vuecs/countdown |
Countdown timer with typed scoped slot | |
@vuecs/gravatar |
Gravatar avatar with fallback support | |
@vuecs/link |
Router-aware anchor — NuxtLink / RouterLink / <a> auto-detection |
|
@vuecs/timeago |
Relative-time display, locale-aware, auto-updating |
Themes
| Package | Version | Description |
|---|---|---|
@vuecs/theme-tailwind |
Tailwind v4 utility classes + twMerge-backed extend() + palette runtime |
|
@vuecs/theme-bootstrap |
Bootstrap 5 classes + --bs-* ↔ --vc-color-* design-token bridge |
|
@vuecs/theme-bulma |
Bulma 1.0+ classes + --bulma-* bridge + palette renderer |
Icon presets
| Package | Version | Description |
|---|---|---|
@vuecs/icons-lucide |
Lucide icon names for every semantic icon slot | |
@vuecs/icons-font-awesome |
Font Awesome 6 Solid icon names |
Integrations
| Package | Version | Description |
|---|---|---|
@vuecs/nuxt |
Theme-agnostic Nuxt module — SSR-safe color mode, palette & locale, auto-imports |
Pick your stack
vuecs doesn't assume Tailwind. Each shipping theme pairs with the CSS stack you already use:
/* Tailwind app */
@import "tailwindcss";
@import "@vuecs/design";
@import "@vuecs/theme-tailwind";
/* Bootstrap app — no Tailwind anywhere */
@import "bootstrap/dist/css/bootstrap.css";
@import "@vuecs/design/standalone";
@import "@vuecs/theme-bootstrap";
/* Bulma app — no Tailwind anywhere */
@import "bulma/css/bulma.css";
@import "@vuecs/design/standalone";
@import "@vuecs/theme-bulma";
The Bootstrap and Bulma themes additionally bridge their framework's native CSS variables (--bs-*, --bulma-*) onto vuecs design tokens — so runtime palette switching re-tints native Bootstrap/Bulma widgets too, not just vuecs components.
Example apps
Four runnable apps under examples/, all mounting the same demo views from a shared catalog — living proof that one codebase reskins across ecosystems:
| App | Stack |
|---|---|
examples/nuxt |
Nuxt 4 + Tailwind theme — SSR color mode, palette & locale |
examples/tailwind |
Vite + Vue 3 + Tailwind theme |
examples/bootstrap |
Vite + Vue 3 + Bootstrap 5 |
examples/bulma |
Vite + Vue 3 + Bulma 1.0+ |
A Playwright visual-regression matrix (3 themes × every demo route) guards against unintended drift.
Contributing
npm ci # install
npm run build # build all packages (Nx, cached)
npm run test # run tests
npm run lint # ESLint — must pass with zero errors
Commits follow Conventional Commits; releases are fully automated via release-please.
License
Made with 💚
Published under Apache 2.0 License.
Related Packages
A package containing hyperscript components for usage in render functions.
Form controls (inputs, checkbox, switch, select, textarea, range slider) on Reka...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 2.0.1 | ^3.x | ||
| 2.0.0 | ^3.5.12 | ||
| 1.1.1 | ^3.3.9 | ||
| 1.1.0 | ^3.3.9 | ||
| 1.0.0 | ^3.3.9 |