A comprehensive React component library with a complete design system, built for modern web applications.
This project is currently in rc (1.0.0-rc.x) and under active development for v2. While it has been widely utilized across various Rytass internal projects, please note that API changes may still occur before the stable release. We recommend pinning to specific versions in production environments.
{
"@mezzanine-ui/core": "1.0.0-rc.3",
"@mezzanine-ui/react": "1.0.0-rc.3",
"@mezzanine-ui/system": "1.0.0-rc.3",
"@mezzanine-ui/icons": "1.0.0-rc.3"
}- Storybook - Interactive component documentation and examples
- Migration Guide - Upgrading from previous versions
| Browser | Minimum Version |
|---|---|
| Google Chrome | 64 (2018) |
| Edge | 79 (2020) |
| Safari | 13.1 (2020) |
| Firefox | 69 (2019) |
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.
Install all required packages:
yarn add @mezzanine-ui/core @mezzanine-ui/react @mezzanine-ui/system @mezzanine-ui/iconsIf 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 # AlternativeCreate 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.palette-variables(light);
@include mzn-system.common-variables(default);
}
// Optional: Dark mode support
[data-theme='dark'] {
@include mzn-system.palette-variables(dark);
}
// Optional: Compact mode support
[data-density='compact'] {
@include mzn-system.common-variables(compact);
}
// Import component styles
@include mzn-core.styles();Import the stylesheet at your app's entry point:
import './main.scss';
function App() {
return <div>Your App</div>;
}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 variant="base-primary" size="main">
<PlusIcon />
Click Me
</Button>
</div>
);
}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 dayjsimport { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';
function App({ children }) {
return <CalendarConfigProviderDayjs locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderDayjs>;
}yarn add momentimport { CalendarConfigProviderMoment, CalendarLocale } from '@mezzanine-ui/react/moment';
function App({ children }) {
return <CalendarConfigProviderMoment locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderMoment>;
}yarn add luxonimport { 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.
| 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 |
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 |
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>
);
}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>
);
}| 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 |
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.
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.palette-variables(light, $custom-palette);
}
[data-theme='dark'] {
@include mzn-system.palette-variables(dark, $custom-palette);
}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);
}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);
}@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);
}| 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) |
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 variant="outlined-primary" size="minor">
<PlusIcon />
With Icon
</Button>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>Requires
CalendarConfigProviderwrapper (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}
/>
);
}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 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.
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 |
useElementHeight |
Track an element's height with ResizeObserver |
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 |
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>;
}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;
}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 |
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'); // 1234567import { getCSSVariableValue } from '@mezzanine-ui/react';
// Read design token values at runtime
const brandColor = getCSSVariableValue('--mzn-color-text-brand');Mezzanine UI v2 has built-in support for light and dark modes:
// In your SCSS
:root {
@include mzn-system.palette-variables(light);
}
[data-theme='dark'] {
@include mzn-system.palette-variables(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>;
}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>;
}Use icons from the @mezzanine-ui/icons package:
import { ChevronDownIcon, PlusIcon, CheckIcon } from '@mezzanine-ui/icons';
function Example() {
return (
<div>
<ChevronDownIcon />
<PlusIcon />
<CheckIcon />
</div>
);
}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
MIT License - see LICENSE for details.