Materializr
오픈소스 파라메트릭 3D CAD로 제약 스케치·솔리드 모델·나사·SVG·텍스트 조각·STL/STEP 내보내기를 지원합니다
An enterprise-class UI design language and React/Angular/Vue UI library
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A comprehensive component library for React, Angular and Vue 3 with a complete design system, built for modern web applications.
@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).README 전문 보기 ↗A comprehensive component library for React, Angular and Vue 3 with a complete design system, built for modern web applications.
Current releases:
{
"@mezzanine-ui/core": "1.2.4",
"@mezzanine-ui/react": "1.7.4",
"@mezzanine-ui/ng": "1.0.0-rc.20",
"@mezzanine-ui/vue": "1.0.0-rc.8",
"@mezzanine-ui/system": "1.0.2",
"@mezzanine-ui/icons": "1.0.2"
}
@mezzanine-ui/ngand@mezzanine-ui/vueare 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).
https://storybook.mezzanine-ui.org/react/ — React canonical URLhttps://storybook.mezzanine-ui.org/angular/ — Angular canonical URLhttps://storybook.mezzanine-ui.org/vue/ — Vue canonical URL| 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.
yarn add @mezzanine-ui/core @mezzanine-ui/react @mezzanine-ui/system @mezzanine-ui/icons
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
Requires Vue 3.5+.
yarn add @mezzanine-ui/core @mezzanine-ui/system @mezzanine-ui/icons
yarn add @mezzanine-ui/vue
yarn add vue lodash
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
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();
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 icon={PlusIcon} iconType="leading" size="main" variant="base-primary">
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 dayjs
import { CalendarConfigProviderDayjs, CalendarLocale } from '@mezzanine-ui/react/dayjs';
function App({ children }) {
return <CalendarConfigProviderDayjs locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderDayjs>;
}
yarn add moment
import { CalendarConfigProviderMoment, CalendarLocale } from '@mezzanine-ui/react/moment';
function App({ children }) {
return <CalendarConfigProviderMoment locale={CalendarLocale.ZH_TW}>{children}</CalendarConfigProviderMoment>;
}
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.
| 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 |
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.
@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.
// 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.
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.
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.
@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');
<!-- 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/vueexports no components. Always use the per-component path (e.g.@mezzanine-ui/vue/button). Seepackages/vue/COMPONENTS.mdfor the full list of import names and paths.
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.
Mezzanine UI v2 uses a two-layer design token system:
#3b82f6, 16px)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.colors(light, $custom-palette);
}
[data-theme='dark'] {
@include mzn-system.colors(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 icon={PlusIcon} iconType="leading" size="minor" variant="outlined-primary">
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 |
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'); // 1234567
import { 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.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>;
}
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 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>
);
}
We welcome contributions! Please see our Development Guidelines for:
MIT License - see LICENSE for details.