@mezzanine-ui/vue

Mezzanine UI Vue 3 component library
25
Install
npm install @mezzanine-ui/vue
Latest Version:1.0.0-rc.7
Vue:3
License:MIT
Last Updated:Oct 27, 2026
Links: GitHub  ·  npm
Maintainer: fantasywind

Mezzanine UI

A comprehensive component library for React, Angular and Vue 3 with a complete design system, built for modern web applications.

📦 Version

Current releases:

{
  "@mezzanine-ui/core": "1.2.4",
  "@mezzanine-ui/react": "1.7.1",
  "@mezzanine-ui/ng": "1.0.0-rc.18",
  "@mezzanine-ui/vue": "1.0.0-rc.5",
  "@mezzanine-ui/system": "1.0.2",
  "@mezzanine-ui/icons": "1.0.2"
}

@mezzanine-ui/ng and @mezzanine-ui/vue are release candidates: the component set is complete and mirrors React prop-for-prop, and the public API may still receive minor adjustments before the stable release. Install them like any other package (see below).

📚 Documentation

  • Storybook — unified hub that links the React, Angular and Vue Storybooks side by side
    • https://storybook.mezzanine-ui.org/react/ — React canonical URL
    • https://storybook.mezzanine-ui.org/angular/ — Angular canonical URL
    • https://storybook.mezzanine-ui.org/vue/ — Vue canonical URL
  • Migration Guide — Upgrading from previous versions

🌐 Browser Support

Browser Minimum Version
Google Chrome 64 (2018)
Edge 79 (2020)
Safari 13.1 (2020)
Firefox 69 (2019)

⚡ Framework Support

Next.js (App Router & Pages Router)

Mezzanine UI fully supports Next.js including the App Router. All React components include the 'use client' directive, making them compatible with React Server Components architecture.

// app/layout.tsx - Works seamlessly with Next.js App Router
import { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <CalendarConfigProviderDayjs locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderDayjs>
      </body>
    </html>
  );
}

All hooks and utilities are also SSR-safe and can be used in Next.js projects without additional configuration.


📦 Installation

React

yarn add @mezzanine-ui/core @mezzanine-ui/react @mezzanine-ui/system @mezzanine-ui/icons

Angular (release candidate)

Requires Angular 21+.

yarn add @mezzanine-ui/core @mezzanine-ui/system @mezzanine-ui/icons
yarn add @mezzanine-ui/ng
yarn add @angular/animations @angular/cdk @angular/common @angular/core

Vue 3 (release candidate)

Requires Vue 3.5+.

yarn add @mezzanine-ui/core @mezzanine-ui/system @mezzanine-ui/icons
yarn add @mezzanine-ui/vue
yarn add vue lodash

Date libraries (all frameworks)

If you plan to use date-related components (DatePicker, Calendar, TimePicker, etc.), install one of the supported date libraries:

# Choose one:
yarn add dayjs      # Recommended - lightweight
yarn add moment     # Legacy support
yarn add luxon      # Alternative

🚀 Quick Start

1. Setup Styles

Create a main.scss file in your project:

@use '@mezzanine-ui/system' as mzn-system;
@use '@mezzanine-ui/core' as mzn-core;

// Apply design system variables
:root {
  @include mzn-system.colors(light);
  @include mzn-system.common-variables(default);
}

// Optional: Dark mode support
[data-theme='dark'] {
  @include mzn-system.colors(dark);
}

// Optional: Compact mode support
[data-density='compact'] {
  @include mzn-system.common-variables(compact);
}

// Import component styles
@include mzn-core.styles();

2. Import Styles

Import the stylesheet at your app's entry point:

import './main.scss';

function App() {
  return <div>Your App</div>;
}

3. Use Components

import { Button, Typography } from '@mezzanine-ui/react';
import { PlusIcon } from '@mezzanine-ui/icons';

function App() {
  return (
    <div>
      <Typography variant="h1">Welcome to Mezzanine UI</Typography>
      <Button icon={PlusIcon} iconType="leading" size="main" variant="base-primary">
        Click Me
      </Button>
    </div>
  );
}

4. Setup CalendarConfigProvider (Required for Date Components)

If your application uses date-related components (DatePicker, DateRangePicker, Calendar, TimePicker, etc.), you must wrap your app with a CalendarConfigProvider. This provider configures the date library and locale settings.

Choose one of the following date libraries based on your project needs:

yarn add dayjs
import { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';

function App({ children }) {
  return <CalendarConfigProviderDayjs locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderDayjs>;
}
Using Moment.js
yarn add moment
import { CalendarConfigProviderMoment, CalendarLocale } from '@mezzanine-ui/react/moment';

function App({ children }) {
  return <CalendarConfigProviderMoment locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderMoment>;
}
Using Luxon
yarn add luxon
import { CalendarConfigProviderLuxon, CalendarLocale } from '@mezzanine-ui/react/luxon';

function App({ children }) {
  return <CalendarConfigProviderLuxon locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderLuxon>;
}

⚠️ Important: Import the provider from the specific entry point (e.g., @mezzanine-ui/react/dayjs) to avoid bundling unused date libraries.


📅 CalendarConfigProvider Configuration

Available Props

Prop Type Default Description
locale CalendarLocaleValue CalendarLocale.EN_US Controls calendar display: first day of week, month/weekday names
defaultDateFormat string 'YYYY-MM-DD' Default format string for date values
defaultTimeFormat string 'HH:mm:ss' Default format string for time values

Supported Locales

Common locale values (see CalendarLocale enum for full list):

Locale Value First Day of Week
CalendarLocale.EN_US 'en-US' Sunday
CalendarLocale.EN_GB 'en-GB' Monday
CalendarLocale.ZH_TW 'zh-TW' Sunday
CalendarLocale.ZH_CN 'zh-CN' Monday
CalendarLocale.JA_JP 'ja-JP' Sunday
CalendarLocale.KO_KR 'ko-KR' Sunday
CalendarLocale.DE_DE 'de-DE' Monday
CalendarLocale.FR_FR 'fr-FR' Monday

Usage with Next.js App Router

For Next.js App Router, place the provider in your root layout:

// app/layout.tsx
import { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <CalendarConfigProviderDayjs locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderDayjs>
      </body>
    </html>
  );
}

Custom Date/Time Formats

import { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';

function App({ children }) {
  return (
    <CalendarConfigProviderDayjs defaultDateFormat="YYYY/MM/DD" defaultTimeFormat="HH:mm" locale={CalendarLocale.ZH_TW}>
      {children}
    </CalendarConfigProviderDayjs>
  );
}

Date Library Comparison

Library Bundle Size Tree-Shakeable ISO Week Support Note
Day.js ~2KB Yes Yes Recommended for most projects
Moment ~70KB No Yes Legacy support
Luxon ~20KB Yes Yes (ISO only) Always uses Monday as first day

🅰️ Angular Quick Start

The Angular package mirrors the React API prop-for-prop. Components are distributed as secondary entry points — import each component from its own path so that Angular's tree-shaker only pulls in what you use.

1. Share the same SCSS setup

@mezzanine-ui/ng consumes the same @mezzanine-ui/core + @mezzanine-ui/system tokens as React. Reuse the main.scss shown in the React Quick Start verbatim and load it from your Angular app's styles array (angular.json) or global stylesheet.

2. Import components from secondary entry points

// app.component.ts
import { Component } from '@angular/core';
import { MznButton } from '@mezzanine-ui/ng/button';
import { MznTypography } from '@mezzanine-ui/ng/typography';
import { MznIcon } from '@mezzanine-ui/ng/icon';
import { PlusIcon } from '@mezzanine-ui/icons';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MznButton, MznTypography, MznIcon],
  template: `
    <mzn-typography variant="h1">Welcome to Mezzanine UI</mzn-typography>
    <mzn-button variant="base-primary" size="main" [icon]="plusIcon" iconType="leading"> Click Me </mzn-button>
  `,
})
export class AppComponent {
  plusIcon = PlusIcon;
}

⚠️ Do not barrel-import from @mezzanine-ui/ng. Always use the per-component path (e.g. @mezzanine-ui/ng/button) so ng-packagr's secondary entry points can be code-split correctly.

3. API parity with React

Angular inputs/outputs mirror React props 1:1, including both flat top-level props (e.g. disabledMonthSwitch, placeholderLeft) and bundle props (e.g. calendarProps, popperProps). When both are provided, flat props win. See .claude/skills/architecting-angular-components/SKILL.md for the full contract.


💚 Vue Quick Start

The Vue package mirrors the React API prop-for-prop. Every component is prefixed with Mzn and distributed as a sub-path entry point — import each component from its own path so that your bundler only pulls in what you use.

1. Share the same SCSS setup

@mezzanine-ui/vue ships no stylesheet of its own; all styles come from @mezzanine-ui/core + @mezzanine-ui/system. Reuse the main.scss shown in the React Quick Start verbatim and import it once in your app entry:

// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import './main.scss';

createApp(App).mount('#app');

2. Import components from sub-path entry points

<!-- App.vue -->
<script setup lang="ts">
import { MznButton } from '@mezzanine-ui/vue/button';
import { MznTypography } from '@mezzanine-ui/vue/typography';
import { PlusIcon } from '@mezzanine-ui/icons';
</script>

<template>
  <MznTypography variant="h1">Welcome to Mezzanine UI</MznTypography>
  <MznButton :icon="PlusIcon" icon-type="leading" size="main" variant="base-primary">Click Me</MznButton>
</template>

⚠️ The package root @mezzanine-ui/vue exports no components. Always use the per-component path (e.g. @mezzanine-ui/vue/button). See packages/vue/COMPONENTS.md for the full list of import names and paths.

3. Setup CalendarConfigProvider (Required for Date Components)

Date-related components (DatePicker, DateRangePicker, Calendar, TimePicker, etc.) read their date library and locale from a MznCalendarConfigProvider. Wrap your app once with the preset provider for the date library you installed — all presets live in @mezzanine-ui/vue/calendar:

Preset Date library
MznCalendarConfigProviderDayjs Day.js
MznCalendarConfigProviderMoment Moment.js
MznCalendarConfigProviderLuxon Luxon
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { CalendarLocale, MznCalendarConfigProviderDayjs } from '@mezzanine-ui/vue/calendar';
import { MznDatePicker } from '@mezzanine-ui/vue/date-picker';

const date = ref<string>();
</script>

<template>
  <MznCalendarConfigProviderDayjs :locale="CalendarLocale.ZH_TW">
    <MznDatePicker :value="date" placeholder="Select a date" @change="(value) => (date = value)" />
  </MznCalendarConfigProviderDayjs>
</template>

The preset providers accept the same locale, defaultDateFormat and defaultTimeFormat props documented in CalendarConfigProvider Configuration.


🎨 Design System Customization

Understanding Primitives vs Semantic

Mezzanine UI v2 uses a two-layer design token system:

  • Primitives: Raw values (e.g., #3b82f6, 16px)
  • Semantic: Contextual tokens that reference primitives (e.g., text-brand, padding-base)

💡 Best Practice: Always use semantic tokens in your application for automatic theme switching support.

Customize Color Palette

Override palette colors by passing a custom configuration:

@use '@mezzanine-ui/system' as mzn-system;

$custom-palette: (
  background: (
    base: (
      light: #000,
      dark: #fff,
    ),
    menu: (
      light: #fff,
      dark: #9a9a9a,
    ),
    // ...
  ), // ...
);

:root {
  @include mzn-system.colors(light, $custom-palette);
}

[data-theme='dark'] {
  @include mzn-system.colors(dark, $custom-palette);
}

Customize Typography

Override typography settings:

@use '@mezzanine-ui/system' as mzn-system;
@use '@mezzanine-ui/system/typography' as typography;

$custom-variables: (
  typography: (
    h3: (
      font-size: 18px,
      font-weight: 700,
      line-height: 26px,
      letter-spacing: 0,
    ),
    // ...
  ),
);

:root {
  @include mzn-system.common-variables(default, $custom-variables);
}

Customize Spacing

Override spacing values:

@use '@mezzanine-ui/system' as mzn-system;

$custom-variables: (
  spacing: (
    size: (
      element: (
        hairline: (
          default: 2px,
          compact: 2px,
        ),
      ),
    ),
  ),
);

:root {
  @include mzn-system.common-variables(default, $custom-variables);
}

[data-density='compact'] {
  @include mzn-system.common-variables(compact, $custom-variables);
}

🎯 Using Design Tokens in Your Components

In SCSS

@use '@mezzanine-ui/system/palette' as palette;
@use '@mezzanine-ui/system/spacing' as spacing;
@use '@mezzanine-ui/system/radius' as radius;
@use '@mezzanine-ui/system/typography' as typography;

.my-component {
  // Colors - use semantic variables
  color: palette.semantic-variable(text, brand);
  background-color: palette.semantic-variable(background, base);
  border-color: palette.semantic-variable(border, neutral);

  // Spacing - use semantic variables
  padding: spacing.semantic-variable(padding, horizontal, base);
  gap: spacing.semantic-variable(gap, tight);

  // Border radius
  border-radius: radius.variable(base);

  // Typography - apply full semantic typography
  @include typography.semantic-variable(body);
}

Available System Modules

Module Purpose Example
palette Colors (text, background, etc.) palette.semantic-variable(text, brand)
spacing Padding, margin, gap spacing.semantic-variable(padding, base)
radius Border radius radius.variable(base)
typography Font settings @include typography.semantic-variable(button)
effect Shadows, focus rings effect.variable(focus, primary)
size Element sizes size.semantic-variable(element, main)

🔧 Using React Components

Component Examples

Button
import { Button } from '@mezzanine-ui/react';
import { PlusIcon } from '@mezzanine-ui/icons';

<Button variant="base-primary" size="main">
  Primary Button
</Button>

<Button variant="base-secondary" size="sub" disabled>
  Disabled Button
</Button>

<Button icon={PlusIcon} iconType="leading" size="minor" variant="outlined-primary">
  With Icon
</Button>
Typography
import { Typography } from '@mezzanine-ui/react';

<Typography variant="h1">Heading 1</Typography>
<Typography variant="body">Body text</Typography>
<Typography variant="caption" color="text-neutral">
  Caption text
</Typography>
DatePicker

Requires CalendarConfigProvider wrapper (see Setup CalendarConfigProvider)

import { useState } from 'react';
import { DatePicker } from '@mezzanine-ui/react';

function Example() {
  const [date, setDate] = useState<string>();

  return (
    <DatePicker
      onChange={setDate}
      placeholder="Select a date"
      value={date}
    />
  );
}
DateRangePicker
import { useState } from 'react';
import { DateRangePicker } from '@mezzanine-ui/react';

function Example() {
  const [range, setRange] = useState<[string, string]>();

  return (
    <DateRangePicker
      onChange={setRange}
      value={range}
    />
  );
}
Layout

Layout is the top-level page shell for building full-application frames. It manages a responsive structure with a sticky navigation sidebar and optional resizable side panels. The sub-components (Layout.Main, Layout.LeftPanel, Layout.RightPanel) can be placed in any order — the layout always renders them in the correct visual sequence.

Sub-component Purpose
<Navigation> Sticky left navigation sidebar
<Layout.Main> Main scrollable content area (required)
<Layout.LeftPanel> Resizable left panel (optional)
<Layout.RightPanel> Resizable right panel (optional)

Basic layout with navigation:

import { useState } from 'react';
import { Layout, Navigation, NavigationFooter, NavigationHeader, NavigationOption } from '@mezzanine-ui/react';
import { FileIcon, HomeIcon } from '@mezzanine-ui/icons';

function App() {
  const [activatedPath, setActivatedPath] = useState(['Home']);

  return (
    <Layout>
      <Navigation activatedPath={activatedPath} onOptionClick={(path) => path && setActivatedPath(path)}>
        <NavigationHeader title="My App" />
        <NavigationOption icon={HomeIcon} title="Home" />
        <NavigationOption icon={FileIcon} title="Reports">
          <NavigationOption title="Traffic" />
          <NavigationOption title="Conversion" />
        </NavigationOption>
        <NavigationFooter />
      </Navigation>
      <Layout.Main>
        <h1>Page Content</h1>
      </Layout.Main>
    </Layout>
  );
}

With a toggleable right panel:

import { useState } from 'react';
import { Layout, Navigation, NavigationFooter, NavigationHeader } from '@mezzanine-ui/react';

function App() {
  const [rightOpen, setRightOpen] = useState(false);

  return (
    <Layout>
      <Navigation>
        <NavigationHeader title="My App" />
        <NavigationFooter />
      </Navigation>
      <Layout.Main>
        <button onClick={() => setRightOpen(true)}>Open Details</button>
      </Layout.Main>
      <Layout.RightPanel defaultWidth={320} open={rightOpen}>
        <div>
          <h2>Details</h2>
          <button onClick={() => setRightOpen(false)}>Close</button>
        </div>
      </Layout.RightPanel>
    </Layout>
  );
}

With dual panels (left + right):

import { useState } from 'react';
import { Layout, Navigation, NavigationFooter, NavigationHeader } from '@mezzanine-ui/react';

function App() {
  const [leftOpen, setLeftOpen] = useState(true);
  const [rightOpen, setRightOpen] = useState(false);

  return (
    <Layout>
      <Navigation>
        <NavigationHeader title="My App" />
        <NavigationFooter />
      </Navigation>
      <Layout.LeftPanel defaultWidth={240} open={leftOpen}>
        <div>Sidebar filters, navigation trees, etc.</div>
      </Layout.LeftPanel>
      <Layout.Main>
        <h1>Main Content</h1>
        {!rightOpen && <button onClick={() => setRightOpen(true)}>Open Right</button>}
      </Layout.Main>
      <Layout.RightPanel defaultWidth={320} open={rightOpen}>
        <div>Detail view, preview, contextual actions, etc.</div>
      </Layout.RightPanel>
    </Layout>
  );
}

Layout.LeftPanel / Layout.RightPanel props:

Prop Type Default Description
open boolean false Controls panel visibility
defaultWidth number 320 Initial width in px (minimum 240)
onWidthChange (width: number) => void — Callback when the panel is resized
scrollbarProps ScrollbarProps — Props forwarded to the inner scrollbar

Panels are resizable by dragging the divider. Focus the divider and use ← / → arrow keys to resize with keyboard.


🪝 React Hooks

Mezzanine provides several utility hooks for common UI patterns:

Hook Description
useClickAway Detect clicks outside an element (useful for dropdowns, modals)
useComposeRefs Compose multiple refs into one
useDocumentEscapeKeyDown Listen for ESC key press on document
useDocumentTabKeyDown Listen for Tab key press on document
useDocumentEvents Generic document event listener with cleanup
useIsomorphicLayoutEffect SSR-safe useLayoutEffect (uses useEffect on server)
useLastCallback Stable callback reference that always calls the latest version
useLastValue Ref that always holds the latest value
usePreviousValue Access the previous render's value
useScrollLock Lock body scroll (for modals/overlays) with scrollbar gap compensation
useTopStack Manage stacking order for overlays
useWindowWidth Track window width with resize listener

Example: useClickAway

import { useRef } from 'react';
import { useClickAway } from '@mezzanine-ui/react';

function Dropdown({ onClose }) {
  const dropdownRef = useRef();

  useClickAway(() => (event) => onClose(event), dropdownRef, [onClose]);

  return <div ref={dropdownRef}>Dropdown content</div>;
}

Example: useScrollLock

import { useScrollLock } from '@mezzanine-ui/react';

function Modal({ isOpen }) {
  // Automatically locks scroll when modal is open
  useScrollLock({ enabled: isOpen });

  return isOpen ? <div className="modal">Modal content</div> : null;
}

🛠️ Utility Functions

Mezzanine also exports commonly used utility functions:

Utility Description
formatNumberWithCommas Format numbers with locale-aware thousands separators
parseNumberWithCommas Parse comma-formatted string back to number
getCSSVariableValue Get computed CSS variable value from :root
getNumericCSSVariablePixelValue Get CSS variable as numeric pixel value
arrayMove Move array item from one index to another (immutable)
cx Classname utility (re-exported from clsx)
composeRefs Compose multiple refs (non-hook version)
getScrollbarWidth Get current scrollbar width in pixels

Example: Number Formatting

import { formatNumberWithCommas, parseNumberWithCommas } from '@mezzanine-ui/react';

// Format number for display
formatNumberWithCommas(1234567); // '1,234,567'
formatNumberWithCommas(1234567, 'de-DE'); // '1.234.567'

// Parse back to number
parseNumberWithCommas('1,234,567'); // 1234567

Example: CSS Variables

import { getCSSVariableValue } from '@mezzanine-ui/react';

// Read design token values at runtime
const brandColor = getCSSVariableValue('--mzn-color-text-brand');

🌙 Theme Support

Light/Dark Mode

Mezzanine UI v2 has built-in support for light and dark modes:

// In your SCSS
:root {
  @include mzn-system.colors(light);
}

[data-theme='dark'] {
  @include mzn-system.colors(dark);
}

Toggle theme in your React app:

function ThemeToggle() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  return <Button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</Button>;
}

Default/Compact Mode

Switch between comfortable and compact spacing:

:root {
  @include mzn-system.common-variables(default);
}

[data-density='compact'] {
  @include mzn-system.common-variables(compact);
}

Toggle density in your React app:

function DensityToggle() {
  const [density, setDensity] = useState('default');

  useEffect(() => {
    document.documentElement.setAttribute('data-density', density);
  }, [density]);

  return <Button onClick={() => setDensity(density === 'default' ? 'compact' : 'default')}>Toggle Density</Button>;
}

📖 Icon Usage

Use icons from the @mezzanine-ui/icons package:

import Icon from '@mezzanine-ui/react/Icon';
import { CheckIcon, ChevronDownIcon, PlusIcon } from '@mezzanine-ui/icons';

function Example() {
  return (
    <div>
      <Icon icon={ChevronDownIcon} />
      <Icon icon={PlusIcon} />
      <Icon icon={CheckIcon} />
    </div>
  );
}

🤝 Contributing

We welcome contributions! Please see our Development Guidelines for:

  • Setting up the development environment
  • Understanding the project architecture
  • Following coding conventions
  • Writing tests and documentation

📝 License

MIT License - see LICENSE for details.


Version History

Pre-releases (8)
Version Released Vue License
1.0.0-rc.7 >=3.5 MIT
1.0.0-rc.6 >=3.5 MIT
1.0.0-rc.5 >=3.5 MIT
1.0.0-rc.4 >=3.5 MIT
1.0.0-rc.3 >=3.5 MIT