@brumaombra/ui-vintage

Source-published Nuxt UI runtime with shared theme tokens and built-in Nuxt integrations.
6,130
Install
npm install @brumaombra/ui-vintage
Latest Version:0.9.12
Vue:3
License:MIT
Last Updated:Oct 22, 2026
Links: GitHub  ·  npm
Maintainer: brumaombra

Why UI Vintage

UI Vintage is a Nuxt 4 module and component library for dashboards, forms, landing pages, overlays, and blogs that should look and feel like one product. It gives you low-level primitives (buttons, inputs, selects, dialogs, tabs) and complete building blocks (dashboard and landing shells, stat cards, data tables, a full blog) that share one set of theme tokens and one motion vocabulary.

  • Source-published. The package ships its Vue and TypeScript source. Your Nuxt app compiles it together with your own code, so there is no separate build artifact and every Nuxt integration (NuxtImg, #components, module hooks) keeps working.
  • Explicit imports. Every component lives behind its own subpath, such as @brumaombra/ui-vintage/button. Nothing is auto-registered, so your dependencies stay obvious and tree-shakeable.
  • Accessible by default. Interactive primitives are built on Reka UI, with keyboard support, focus management, and ARIA wiring included.
  • Motion that respects people. Springs are pure CSS linear() easings, and every animation collapses when the user prefers reduced motion.

Features

  • Nuxt 4 module that injects the stylesheet, transpiles the source, installs @nuxt/image when needed, and merges the library locale messages into Vue I18n.
  • Shared design tokens for colors, borders, and elevation, with light, dark, and automatic themes and a circular View Transitions reveal when switching.
  • Primitives on Reka UI: select, combobox, dialog, sheet, popover, tooltip, tabs, stepper, slider, calendar, date and time pickers, PIN input, tags input, and more.
  • Composite building blocks: dashboard and landing shells, page headers, stat cards and stat strips, data tables with animated sorting, card grids with empty and loading states.
  • Promise-based flows: showConfirmDialog, showMessageDialog, stacked and swipeable showMessageToast, and a shared busy overlay with setBusy.
  • A complete blog for Nuxt Content: hero header, featured carousel, post header, table of contents, share sidebar, FAQ, reading progress, and VS Code-style code blocks.
  • Localization in English, Italian, French, Spanish, German, Portuguese, Chinese, Japanese, and Russian.
  • Ready for AI coding agents: a generated component catalog (COMPONENTS.md) and agent instructions (AGENTS.md) ship inside the package.

Installation

Install the package with its peer integrations in an existing Nuxt 4 app. The stylesheet is built with Tailwind CSS v4, the components use Vue I18n for their labels, and icons come from Hugeicons:

npm install @brumaombra/ui-vintage @nuxt/image @nuxtjs/i18n tailwindcss @tailwindcss/vite @hugeicons/core-free-icons @hugeicons/vue

Register the module, Nuxt I18n, and the Tailwind Vite plugin in nuxt.config:

import tailwindcss from '@tailwindcss/vite';

export default defineNuxtConfig({
    modules: [
        '@brumaombra/ui-vintage',
        '@nuxtjs/i18n'
    ],

    vite: {
        plugins: [
            tailwindcss()
        ]
    },

    i18n: {
        defaultLocale: 'en',
        locales: [{ code: 'en', language: 'en-US' }]
    }
});

That's it: the module injects the shared stylesheet, adds the source to Nuxt transpilation, installs @nuxt/image when your app has not registered it, and merges the library messages into your i18n setup. The components call useI18n(), so an i18n setup is required even for a single-language app. Using the blog or content components? Install @nuxt/content too.

Usage

Use a component

Import components from their public subpath:

<script setup>
import { Button } from '@brumaombra/ui-vintage/button';
import { SingleValueCard } from '@brumaombra/ui-vintage/single-value-card';
import Money03Icon from '@hugeicons/core-free-icons/Money03Icon';
</script>

<template>
    <SingleValueCard label="Revenue" :value="48290" :icon="Money03Icon" :trend="12.4" trend-label="vs last month"
        :format-options="{ style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }" />

    <Button variant="primary">Save changes</Button>
</template>

Ask, confirm, and notify

Dialogs, toasts, and the busy overlay mount themselves on first use, so you never place them in a layout:

import { showConfirmDialog } from '@brumaombra/ui-vintage/confirm-dialog';
import { showMessageToast } from '@brumaombra/ui-vintage/message-toast';
import { setBusy } from '@brumaombra/ui-vintage/busy-indicator';

// Resolves true or false with the user's choice
const confirmed = await showConfirmDialog({
    title: 'Delete project?',
    message: 'This action cannot be undone.',
    confirmButtonType: 'red'
});

if (confirmed) {
    // Keep the shared loading overlay visible while the request runs
    setBusy(true, { label: 'Deleting project...' });
    await deleteProject();
    setBusy(false);

    showMessageToast({ type: 'success', title: 'Project deleted', message: 'Everything was removed.' });
}

Build a layout

Shells own the layout and responsive behavior; your app owns the navigation data and the content. Links take plain paths (to: '/settings'): the components render NuxtLinkLocale, which adds the current language prefix and navigates client-side, and leaves external URLs as they are:

<script setup>
import { DashboardShell } from '@brumaombra/ui-vintage/dashboard-shell';

// Sidebar sections and their links
const sidebarSections = [{
    id: 'workspace',
    label: 'Workspace',
    items: [
        { id: 'overview', label: 'Overview', to: '/', active: true },
        { id: 'settings', label: 'Settings', to: '/settings' }
    ]
}];
</script>

<template>
    <DashboardShell app-name="Acme" app-logo="/logo.svg" app-logo-dark="/logo-dark.svg" :sidebar-sections="sidebarSections">
        <slot />
    </DashboardShell>
</template>

Showcase

Every screenshot comes from the demo app, and each one follows your GitHub theme (light or dark).

Components

84 entry points, grouped as in the catalog. Each name is also its import subpath, for example @brumaombra/ui-vintage/data-table.

Category Entry points
Actions button, dropdown-menu, kbd, load-more-button, text-link, toggle-group
Forms calendar, checkbox, combobox, date-picker, date-time-picker, field, file-dropzone, input, label, native-select, number-field, pin-input, radio-group, select, slider, slider-form-component, switch, switch-form-component, tags-input, textarea, time-picker
Data display animated-number, avatar, badge, card, card-grid, chip, data-list, data-table, info-card, progress, progress-component, scroll-area, separator, single-value-card, skeleton, stat-strip, table
Navigation accordion, breadcrumb, collapsible, pagination, sidebar, stepper, tabs
Overlays alert-dialog, command, dialog, hover-card, popover, sheet, tooltip
Feedback alert, busy, busy-indicator, confirm-dialog, cookie-consent, empty-state-card, loading-state-card, message-dialog, message-toast, spinner
Layouts and app shell background-grid, dashboard-shell, error-page, landing, landing-content, landing-footer, landing-navbar, landing-shell, language-flag, language-selector, page-header, theme-selector
Content and blog blog, content
Utilities and types common-types, utils

Component catalog

COMPONENTS.md documents every entry point: its import line, props and defaults, events, slots, helpers, and examples. It is generated from the source and ships inside the package, so your app can always read the version it installed at node_modules/@brumaombra/ui-vintage/COMPONENTS.md.

Using UI Vintage with AI coding agents

The package ships AGENTS.md, a short set of instructions that teaches coding agents to reuse UI Vintage: read the catalog before writing UI, pick the right component, follow the library conventions, and check the result.

Point your agent to it from your app's own instructions file, so it always reads the version you installed:

  • Codex, Cursor, GitHub Copilot, Gemini, and other tools that read AGENTS.md: add this line to the AGENTS.md at the root of your app:

    Before writing UI, read and follow node_modules/@brumaombra/ui-vintage/AGENTS.md.
    
  • Claude Code: import it in your app's CLAUDE.md:

    @node_modules/@brumaombra/ui-vintage/AGENTS.md
    

Both the instructions and the catalog update with the package, so you never need to copy them again.

Theming

Tokens and dark mode

The stylesheet defines semantic tokens (background, foreground, card, primary, muted, border, and more) for both themes. Dark mode follows the .dark class on <html>; ThemeSelector sets it for you, with light, dark, and automatic modes. Extend the existing CSS custom properties in your app stylesheet instead of creating a second token system.

The module injects the stylesheet automatically. Import it explicitly only when you need to control the loading order:

import '@brumaombra/ui-vintage/style.css';

LandingNavbar, LandingFooter, and DashboardShell accept app-logo and app-logo-dark, so your logo can follow the theme too.

Motion

Every component uses the same small vocabulary, and your app can reuse it:

  • Easings: ease-spring, ease-bounce, ease-out-expo, and ease-snappy.
  • Surfaces: uv-floating-motion (popovers, menus, tooltips), uv-modal-motion, uv-overlay-motion, uv-collapsible-motion, and uv-field (focus ring and invalid states for inputs).
  • Animations: animate-uv-pop, animate-uv-fade-up, animate-uv-shake, animate-uv-shimmer, animate-uv-float, animate-uv-ping-soft, animate-uv-word-in, animate-uv-grow-x, and animate-uv-draw.
  • Elevation: shadow-elevated-sm through shadow-elevated-xl.

All durations collapse when the user prefers reduced motion. Tailwind v4 animates scale, rotate, and translate as separate properties, so name them (not transform) in custom transitions.

Localization

Library strings live under the uiVintage namespace and are available in English, Italian, French, Spanish, German, Portuguese, Chinese, Japanese, and Russian. With @nuxtjs/i18n or Vue I18n configured, the runtime plugin merges them into your existing composer without replacing your messages.

How it works

When the module is registered, it:

  1. Injects src/styles.css into the app once.
  2. Adds the published src/ directory to Nuxt transpilation.
  3. Installs @nuxt/image when the app has not registered it.
  4. Registers the library i18n plugin, which merges the locale messages when Vue I18n is present.

The module does not auto-register components. Always import from a public subpath; files under src/components/ are implementation details and may change between versions.

src/
  components/   Public components and UI primitives
  i18n/         Library locale messages
  lib/          Shared helpers and token utilities
  runtime/      Nuxt runtime plugin
  styles.css    Design tokens, motion, and component styles
module.mjs      Nuxt module entry point
demo-app/       Private showcase app (not published)

Demo app

demo-app/ is a documentation-style Nuxt 4 app that shows every component with a live preview and a copyable code tab, a live mini-app on the overview page, a complete blog built with Nuxt Content, and a command palette (Ctrl/⌘ + K) to jump to any section. It imports the package through file:.., so it runs the same source that is published to npm.

It needs Node.js 24.21 or newer. Nuxt Content uses the built-in node:sqlite module instead of a native SQLite build.

# Install and start the demo
npm --prefix demo-app install
npm --prefix demo-app run dev

# Production build
npm --prefix demo-app run build

Development

The tests live in demo-app/tests: unit covers the helpers, the dialog and toast state, theme storage, and the locale files in plain Node, while app mounts the components inside the demo Nuxt app with @nuxt/test-utils.

There is no build step: the published files are module.mjs, src/, COMPONENTS.md, and AGENTS.md. When a version tag is pushed, the GitHub Actions workflow runs the type check, the catalog check, and the tests, and publishes to npm only if they pass. Before opening a pull request or publishing, run the same checks locally:

# Type-check the published TypeScript and Vue source
npm run typecheck

# Run the tests (unit tests in Node, component tests inside the demo Nuxt app)
npm test

# Regenerate the component catalog after changing a public API
npm run docs:components

# Fail when COMPONENTS.md is out of date
npm run docs:components:check

New entry points need an export in package.json and a description and category in scripts/components-meta.mjs; the catalog generator fails until they have one. The release process is described in .claude/skills/publish-npm/SKILL.md.

Requirements

  • Nuxt 4 and Vue 3.5 or newer.
  • Tailwind CSS v4 with @tailwindcss/vite registered in vite.plugins.
  • @hugeicons/core-free-icons 4.3 or newer and @hugeicons/vue 1 (the icons you pass to :icon props, and the ones the components render). Import each icon from its own module, as in the example above: the package barrel makes the build parse the whole icon set.
  • @nuxtjs/i18n 10 (the components call useI18n(), and every link renders NuxtLinkLocale).
  • @nuxt/image 2 for image components (the module installs it when needed).
  • @nuxt/content 3, only for the blog and content components.

Troubleshooting

  • Components can't resolve Nuxt imports: check that the app runs Nuxt 4 and that @brumaombra/ui-vintage is registered in nuxt.config.
  • Styles are missing: register the module once, add tailwindcss() from @tailwindcss/vite to vite.plugins, and restart the dev server after changing nuxt.config.
  • NuxtImg is unavailable: install @nuxt/image, or let the module install it during setup.
  • useI18n errors, missing translations, or NuxtLinkLocale failing to resolve: register @nuxtjs/i18n; the components need its composer and link component, and the library messages are merged into it.
  • An import fails: use the public subpath from package.json, such as @brumaombra/ui-vintage/button, never an internal src/ path.

License

MIT © Mauro Brambilla

Related Packages

@pisandelli/daredash

Provides a design token-powered UI component library and styling system for Nuxt...

6,023 3
nuxt-yup

yup implementation for nuxt

2,981 4
@solar-icons/nuxt

Nuxt module for easily integrating Solar Icons into your Nuxt 3+ applications.

9,600 224
@smurfox/proxy-ui

A UI component library built for Nuxt 4

6,698 0
nacr-design

NacrDesign - A Nuxt 3/4 UI Component Library

3,291 2

Version History

Version Released License
0.9.12 MIT
0.9.11 MIT
0.9.10 MIT
0.9.9 MIT
0.9.8 MIT