Skip to content

Theming ​

Modern and performant theme system for Maz-UI built on native CSS features (light-dark(), color-scheme, color-mix(in oklch)) for v5.

@maz-ui/themes

Features ​

  • Native light-dark() + color-scheme - Modern CSS theming with zero JS overhead for the light/dark switch
  • Native widget theming - color-scheme makes scrollbars, native <select>, date pickers and autofill follow the active mode automatically
  • Animated theme switch - Optional full-page View Transitions via setColorMode(..., { animate: true })
  • Anti-FART - <meta name="color-scheme"> injected at boot to prevent any Flash of inAccurate coloR Theme (no setup needed)
  • Automatic color scales - --maz-X-50 through --maz-X-950 derived via OKLCh relative color syntax — perceptually uniform and chroma-preserving
  • Performance Strategies - Runtime injection or build-time generation according to your needs
  • Strict TypeScript - Complete types for perfect DX
  • Zero FOUC - Pass the preset object up front; the full CSS is injected synchronously on first paint
  • Flexible Presets - Ready-to-use and customizable configurations

Theme Editor ​

Create your own theme with the Theme Editor.

Quick Usage ​

1. Plugin Configuration with MazUi plugin ​

To avoid FOUC (Flash of Unstyled Content), you should provide the theme preset.

typescript
// main.ts
import { MazUi } from 'maz-ui/plugins/maz-ui'
import { mazUi } from '@maz-ui/themes/presets/mazUi'
import { createApp } from 'vue'

const app = createApp(App)

app.use(MazUi, {
  theme: {
    preset: mazUi, // pristine | ocean | obsidian | nova
    strategy: 'runtime', // 'runtime' | 'buildtime'
    darkModeStrategy: 'class', // 'class' | 'media' (only if mode is `both`)
    mode: 'both', // 'light' | 'dark' | 'both' (supported color modes)
    colorMode: 'auto', // 'auto' | 'light' | 'dark' (initial color mode, only if mode is 'both')
    // Class added to <html> when dark mode is forced (default: 'dark')
    darkClass: 'dark',
    // Class added to <html> when light mode is forced (default: 'light')
    lightClass: 'light',
    persistPreset: true, // remember the active preset name across reloads
  }
})

Alternative: MazUiProvider

You can also initialize the theme via the MazUiProvider component for lazy-loaded pages or subtree-scoped themes.

2. Setup your CSS to support theme foundation and dark mode ​

css
html {
  font-size: var(--maz-base-font-size);
  font-family: var(--maz-font-family);
  background-color: var(--maz-surface);
  color: var(--maz-foreground);
}

3. Using in your components ​

vue
<script setup>
import { useTheme } from '@maz-ui/themes'

const { toggleDarkMode, isDark, updateTheme } = useTheme()
</script>

<template>
  <div class="maz:bg-surface maz:text-foreground">
    <button
      class="maz:rounded-md maz:bg-primary maz:text-primary-foreground"
      @click="toggleDarkMode"
    >
      {{ isDark ? '☀️' : '🌙' }} Toggle theme
    </button>
  </div>
</template>

Configuration ​

  • preset: The theme preset to use
  • overrides (optional): Override specific parts of the theme
  • strategy (optional, default 'runtime'): The rendering strategy to use — 'runtime' | 'buildtime'
  • darkModeStrategy (optional, default 'class'): The dark mode strategy — 'class' | 'media', only if you use mode both
  • mode (optional, default 'both'): The supported color modes — 'light' | 'dark' | 'both'
  • colorMode (optional, default 'auto'): The initial color mode — 'light' | 'dark' | 'auto' (only if mode is 'both')
  • darkClass (optional, default 'dark'): Class added to <html> when colorMode === 'dark'
  • lightClass (optional, default 'light'): Class added to <html> when colorMode === 'light' — mirror of darkClass
  • persistPreset (optional, default true): Persist the active preset name in the maz-preset cookie so it is restored on reload.

Preset persistence ​

When persistPreset is enabled (default), @maz-ui/themes stores the resolved preset name in a maz-preset cookie (1-year TTL, SameSite=Lax) — exactly like maz-color-mode for the dark/light state. The cookie is:

  • Read at boot and used to resolve the active preset — even when options.preset is provided. options.preset (string name or preset object) is treated as the default the app boots with, while the cookie carries the user's last explicit choice. The cookie wins.
  • Written after every successful preset resolution and after every useTheme().updateTheme() call (idempotent — no write if the value already matches).
  • Auto-cleared when the saved name no longer resolves (e.g. typo, preset removed in a downgrade) — the runtime falls back to options.preset (or default) and clears the cookie silently.

Custom preset names are stored exactly like bundled ones: @maz-ui/themes does not maintain a whitelist. Switch the option off to disable any cookie read/write:

typescript
app.use(MazUi, {
  theme: {
    preset: mazUi,
    persistPreset: false, // never write or read the maz-preset cookie
  },
})

Useful when:

  • You want zero theme-related cookies (privacy / regulatory).
  • The active preset is fully driven by the consumer app and no end-user switching is exposed.

Color modes and class toggling ​

darkClass and lightClass interact with darkModeStrategy to control how <html> is decorated when the user picks an explicit mode.

darkModeStrategy: 'class' (default) ​

  • setColorMode('dark') adds darkClass (default .dark) to <html> → color-scheme: only dark.
  • setColorMode('light') adds lightClass (default .light) to <html> → color-scheme: only light.
  • setColorMode('auto') removes both classes → color-scheme: light dark lets the browser follow the system prefers-color-scheme.

Forcing both classes on the root ensures native widgets (scrollbars, native <select>, date pickers, autofill backgrounds, …) match the explicit user choice rather than the OS preference.

darkModeStrategy: 'media' ​

  • No class is ever added to <html>.
  • The browser always follows prefers-color-scheme via color-scheme: light dark.
  • setColorMode() still updates the persisted cookie but does not force a visual override.

Animated theme switch ​

For a full-page animated swap (rather than per-variable color tweens), pass { animate: true } to setColorMode or toggleDarkMode:

vue
<script setup>
import { useTheme } from '@maz-ui/themes'

const { toggleDarkMode, setColorMode } = useTheme()

async function onToggle() {
  await toggleDarkMode({ animate: true })
}

async function forceDark() {
  await setColorMode('dark', { animate: true })
}
</script>
  • Wraps the switch in document.startViewTransition() so the browser captures a snapshot of the page before/after and crossfades between them.
  • The View Transition glue is lazy-imported — when animate is not used, it stays out of the boot bundle.
  • Graceful degradation: in browsers that don't support the API, the change is applied immediately with no animation (no error thrown).

Interactive Demo ​

Real-time theme control

Available Presets ​

Maz-UI (Default) ​

The default theme inspired by Maz-UI identity with vibrant and modern colors.

typescript
import { mazUi } from '@maz-ui/themes/presets'

Pristine ​

A minimalist and elegant theme inspired by Apple design with pure black and white aesthetics.

typescript
import { pristine } from '@maz-ui/themes/presets'

Ocean ​

A vibrant ocean-inspired theme with deep blues, aquatic greens, and coral accents. Features rounded borders and Poppins font for a fresh, modern look.

typescript
import { ocean } from '@maz-ui/themes/presets'

Obsidian ​

A dark and elegant theme with a focus on readability and minimalism.

typescript
import { obsidian } from '@maz-ui/themes/presets'

Nova ​

A modern startup / AI / creative preset — electric violet primary, hot coral secondary, cyan accent. Tight 0.5rem md radius, Geist + Inter font stack, snappy ease-out spring. Built for product / AI surfaces (think Linear, Vercel, OpenAI energy) where the UI itself should feel alive.

typescript
import { nova } from '@maz-ui/themes/presets'

Rendering Strategies ​

CSS is generated and injected synchronously on first paint. Pass the preset object directly (preset: mazUi) so the full stylesheet is rendered before the first frame — no FOUC.

typescript
import { mazUi } from '@maz-ui/themes/presets'

app.use(MazUi, {
  theme: {
    preset: mazUi,
    strategy: 'runtime'
  }
})

🏗️ Buildtime ​

CSS generated at build-time and included in the bundle. You have to build your CSS files before running your app.

See Build-time for more details.

typescript
import { mazUi } from '@maz-ui/themes/presets'

app.use(MazUi, {
  theme: {
    preset: mazUi,
    strategy: 'buildtime'
  }
})

Creating Custom Themes ​

Basic Theme ​


Create your own theme

Default theme

Advanced Theme with Overrides ​

typescript
import { definePreset } from '@maz-ui/themes'

const brandTheme = await definePreset({
  base: 'maz-ui',
  overrides: {
    name: 'brand',
    foundation: {
      'border-width': '2px',
      'font-family': 'Inter, sans-serif'
    },
    scales: {
      rounded: { md: '0.75rem' }
    },
    colors: {
      light: {
        primary: 'oklch(0.6 0.18 254)',
        secondary: 'oklch(0.96 0.01 254)',
        surface: 'oklch(0.99 0.005 254)',
        accent: 'oklch(0.7 0.22 305)'
      },
      dark: {
        primary: 'oklch(0.7 0.18 254)',
        secondary: 'oklch(0.22 0.03 254)',
        surface: 'oklch(0.18 0.02 254)',
        accent: 'oklch(0.78 0.2 305)'
      }
    }
  }
})

Customizing scales ​

Each ThemeScales entry maps to a Tailwind utility — override the keys you care about and the rest stays untouched:

typescript
const denseTheme = definePreset({
  base: 'maz-ui',
  overrides: {
    name: 'dense',
    foundation: {
      // Tighter spacing — every p-N / gap-N / m-N rescales
      space: '0.2rem',
    },
    scales: {
      // Sharper corners on the whole radius scale
      rounded: {
        'xs': '0.0625rem',
        'sm': '0.125rem',
        'md': '0.375rem',
        'lg': '0.5rem',
        'xl': '0.75rem',
        '2xl': '1rem',
        '3xl': '1.5rem',
      },
      // Custom elevated-surface shadow
      shadow: {
        elevation: '0 8px 24px rgba(0, 0, 0, 0.15)',
      },
    },
  },
})

Foundation gets new optional keys for fonts and disabled-state behaviour:

typescript
const codeTheme = definePreset({
  base: 'maz-ui',
  overrides: {
    foundation: {
      'font-mono-stack': '"JetBrains Mono", ui-monospace, SFMono-Regular, monospace',
      'font-display-stack': '"Cal Sans", Manrope, sans-serif',
      'disabled-opacity': '0.4',
      'disabled-cursor': 'wait',
    },
  },
})

Per-component overrides ​

components is a small, validated set of per-component knobs. Every entry is per-mode where appropriate so light and dark can drift independently:

typescript
const surfaceTheme = definePreset({
  base: 'maz-ui',
  overrides: {
    name: 'cool-surfaces',
    components: {
      btn: {
        // Bias every button text heavier
        'font-weight': '600',
      },
      container: {
        // Cards / dialogs / popovers / drawers — light = page surface, dark = a tier above
        bg: { light: 'var(--maz-surface)', dark: 'var(--maz-surface-300)' },
      },
      input: {
        // All form-control surfaces
        bg: { light: 'var(--maz-surface-100)', dark: 'var(--maz-surface-400)' },
      },
    },
  },
})

Components consume these via maz:bg-container / maz:bg-input Tailwind utilities, so a single override propagates everywhere — no per-component class hunt.

Dialog sizing ​

components.dialog controls the width of MazDialog on tablet and up. The two keys are emitted as --maz-dialog-max-width / --maz-dialog-min-width and consumed by the component through var(--maz-dialog-max-width, 38rem) / var(--maz-dialog-min-width, 32rem):

typescript
const wideDialogTheme = definePreset({
  base: 'maz-ui',
  overrides: {
    name: 'wide-dialogs',
    components: {
      dialog: {
        'max-width': '48rem',
        'min-width': '36rem',
      },
    },
  },
})

Both values are in rem, so they scale with the preset's base-font-size. Bundled presets anchored on 14px use 38rem / 32rem; ocean (16px) ships scaled rem values (33.25rem / 28rem) to keep the same rendered pixel width. The max-width / min-width props on MazDialog still override the preset per instance.

useTheme Composable API ​

typescript
const {
  // Reactive state
  preset, // ComputedRef<ThemePreset>
  presetName, // ComputedRef<string>
  colorMode, // Ref<'light' | 'dark' | 'auto'> — also accepts assignment (computed setter)
  isDark, // ComputedRef<boolean>
  strategy, // ComputedRef<'runtime' | 'buildtime'>
  mode, // ComputedRef<'light' | 'dark' | 'both'>
  darkModeStrategy, // ComputedRef<'class' | 'media'>

  // Actions
  updateTheme, // (preset: ThemePreset | ThemePresetName | ThemePresetOverrides) => Promise<void>
  setColorMode, // (mode: 'light' | 'dark' | 'auto', options?: { animate?: boolean }) => Promise<void>
  toggleDarkMode, // (options?: { animate?: boolean }) => Promise<void>
} = useTheme()

// Set the color mode imperatively
await setColorMode('dark')

// Or assign to `colorMode` — it's a computed with a setter
colorMode.value = 'dark'

// Animated full-page switch via View Transitions
await toggleDarkMode({ animate: true })

setColorMode and toggleDarkMode are async (Promise<void>). The promise resolves once the change — and the optional View Transition — has been applied. Callers can ignore the return value when no transition is needed.

Advanced Usage Examples ​

vue
<script setup>
import { useTheme } from '@maz-ui/themes'
import { computed } from 'vue'

const { presetName, isDark, updateTheme, setColorMode } = useTheme()

// Computed for interface
const themeIcon = computed(() => isDark.value ? '☀️' : '🌙')

// Function to apply a temporary custom theme
function previewColor(color: string) {
  updateTheme({
    colors: {
      light: { primary: color },
      dark: { primary: color }
    }
  })
}

// Auto mode with system detection
function enableAutoMode() {
  setColorMode('auto')
}
</script>

Build-time Strategy: Complete Guide ​

The build-time strategy allows you to generate your theme CSS files at compile time, offering the best performance in production.

Step-by-step configuration ​

1. Generate CSS ​

typescript
import { mkdirSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import {
  // Build all css in one file
  buildThemeCSS,
  // Build multiple themes with one file each
  generateThemeBundle,
  // Build separate css files
  buildSeparateThemeFiles,
  definePreset,
  mazUi,
} from '@maz-ui/themes'

import App from './App.vue'

const _dirname = dirname(fileURLToPath(import.meta.url))

// Custom theme
const customPreset = definePreset({
  base: mazUi,
  overrides: {
    name: 'custom',
    colors: {
      light: { primary: 'oklch(0.6 0.18 254)' },
      dark: { primary: 'oklch(0.7 0.18 254)' },
    },
  },
})

/**
 * Generate complete CSS
 */
const fullCSS = buildThemeCSS({
  preset: customPreset,
  mode: 'both',
})

writeFileSync(join(_dirname, 'public/custom.css'), fullCSS)

/**
 * Or generate per-mode CSS files
 */
const { full, lightOnly, darkOnly } = buildSeparateThemeFiles(customPreset, {
  darkSelector: 'class',
})

writeFileSync(join(_dirname, 'public/custom-full.css'), full)

/**
 * Or generate bundle for multiple themes in one file each
 */
const themeBundle = generateThemeBundle([customPreset, mazUi], {
  mode: 'both',
})

Object.entries(themeBundle).forEach(([name, css]) => {
  writeFileSync(join(_dirname, `public/${name}.css`), css)
})

const app = createApp(App)

app.use(MazUiTheme, {
  preset: customPreset,
  strategy: 'buildtime', // Important!
  darkModeStrategy: 'class',
})

app.mount('#app')
typescript
import {
  buildThemeCSS,
  generateThemeBundle,
  buildSeparateThemeFiles,
  createThemeStylesheet,
  definePreset,
  mazUi,
  CSS_ID,
} from '@maz-ui/themes'

// Custom theme
const customPreset = definePreset({
  base: mazUi,
  overrides: {
    name: 'custom',
    colors: {
      light: { primary: 'oklch(0.6 0.18 254)' },
      dark: { primary: 'oklch(0.7 0.18 254)' },
    },
  },
})

// Generate complete CSS
const fullCSS = buildThemeCSS({
  preset: customPreset,
  mode: 'both',
})

// Or generate per-mode CSS files
const { full, lightOnly, darkOnly } = buildSeparateThemeFiles(customPreset, {
  darkSelector: 'class',
})

// Or generate bundle for multiple themes
const themeBundle = generateThemeBundle([customPreset, mazUi], {
  mode: 'both',
})

export default defineConfig<DefaultTheme.Config>({
  head: [
    ['style', { id: CSS_ID, type: 'text/css' }, full],
  ] satisfies HeadConfig[],
})
typescript
/*
 * You dont need to do anything,
 * The module will handle it for you.
 *
 * Just provide a preset to the module
 */

const customPreset = definePreset({
  base: mazUi,
  overrides: {
    name: 'custom',
    colors: {
      light: { primary: 'oklch(0.6 0.18 254)' },
      dark: { primary: 'oklch(0.7 0.18 254)' },
    },
  },
})

export default defineConfig({
  modules: ['@maz-ui/nuxt'],
  mazUi: {
    theme: {
      preset: customPreset,
      mode: 'both',
      colorMode: 'auto',
    },
  },
})

2. Include in your HTML (Only for Vue users) ​

html
<link rel="stylesheet" href="/themes/custom-full.css">

Utility functions ​

buildThemeCSS(options) ​

Generates complete CSS for a theme.

typescript
const css = buildThemeCSS({
  preset: customPreset,
  mode: 'both', // 'light' | 'dark' | 'both'
  darkSelector: 'class', // 'class' | 'media'
})

generateThemeBundle(presets, options) ​

Generates a bundle containing multiple themes.

typescript
const bundle = generateThemeBundle([theme1, theme2], {
  mode: 'both',
  darkSelector: 'class',
})
// Result: { 'theme1': 'css...', 'theme2': 'css...' }

buildSeparateThemeFiles(preset, options) ​

Generates separate files for different use cases.

typescript
const files = buildSeparateThemeFiles(preset)
// Result: { full, lightOnly, darkOnly }

createThemeStylesheet(css, options) ​

Creates a <style> @reference "../../.vitepress/theme/main.css"; tag with the provided CSS.

typescript
const styleTag = createThemeStylesheet(css, {
  id: 'my-theme',
  media: 'screen', // optional
})

Build-time strategy advantages ​

  • Optimal performance - No client-side CSS generation
  • Efficient caching - CSS files cached by CDN
  • Reduced bundle - Generation utilities excluded from client
  • Full compatibility - Works even with JavaScript disabled
  • Production applications with static themes
  • Sites with critical performance requirements
  • Projects with multiple predefined themes
  • Applications requiring fine control over CSS loading

Token Reference ​

Every preset emits the same set of CSS variables on :root. With mode: 'both', each base color is a single light-dark(L, D) declaration that the browser resolves to the active scheme. Use these names directly in your own CSS — var(--maz-primary), calc(var(--maz-space) * 4), etc.

Foundation ​

Single-value design tokens. Set via foundation.<key> on the preset.

Preset keyCSS variableDefaultNotes
base-font-size--maz-base-font-size14pxAnchors rem for the whole app. Apply on html { font-size: var(--maz-base-font-size) }.
border-width--maz-border-width1pxDefault border for components and the global *::before/::after reset.
space--maz-space0.25remBase step for every Tailwind spacing utility (maz:p-1, maz:gap-2, …).
font-family--maz-font-familypreset-specificSans / body font stack. Bridged to Tailwind --font-sans.
font-mono-stack--maz-font-mono-stackui-monospace, …Monospace stack — MazInputCode, <code>, <kbd>. Bridged to --font-mono.
font-display-stack--maz-font-display-stacksame as font-familyDisplay / heading stack. Bridged to --font-display.
motion-fast--maz-motion-fast100msBridged to Tailwind --duration-fast.
motion-normal--maz-motion-normal200msDefault transition duration (--default-transition-duration).
motion-slow--maz-motion-slow300msBridged to --duration-slow.
easing-out--maz-easing-outcubic-bezier(0.4, 0, 0.2, 1)Default easing (--ease-out, also --default-transition-timing-function).
easing-in--maz-easing-incubic-bezier(0.4, 0, 1, 1)Bridged to --ease-in.
easing-in-out--maz-easing-in-outcubic-bezier(0.4, 0, 0.2, 1)Bridged to --ease-in-out.
disabled-opacity--maz-disabled-opacity0.5Applied to disabled buttons / inputs.
disabled-cursor--maz-disabled-cursornot-allowedCursor for disabled interactive elements.

Why font-mono-stack / font-display-stack / motion-*? Under Tailwind v4 prefix(maz), the bridge LHS gets prefixed (--font-mono → --maz-font-mono), so a same-named source token would self-cycle. The -stack and motion- suffixes break the collision.

Colors ​

Defined per mode under colors.light / colors.dark. Each base color also gets an automatic 50–950 scale.

Preset keyCSS variable (base)Auto scaleNotes
surface--maz-surface--maz-surface-50..900Page / container background.
foreground--maz-foreground--maz-foreground-50..900Default text color.
primary--maz-primary--maz-primary-50..900Brand color.
primary-foreground--maz-primary-foreground—Text on bg-primary.
secondary--maz-secondary--maz-secondary-50..900
secondary-foreground--maz-secondary-foreground—
accent--maz-accent--maz-accent-50..900
accent-foreground--maz-accent-foreground—
info--maz-info--maz-info-50..900Status — informational.
info-foreground--maz-info-foreground—
success--maz-success--maz-success-50..900Status — success.
success-foreground--maz-success-foreground—
warning--maz-warning--maz-warning-50..900Status — warning.
warning-foreground--maz-warning-foreground—
destructive--maz-destructive--maz-destructive-50..900Status — error / danger.
destructive-foreground--maz-destructive-foreground—
contrast--maz-contrast--maz-contrast-50..900High-contrast accent (light = near-black, dark = near-white).
contrast-foreground--maz-contrast-foreground—
divider--maz-divider--maz-divider-50..900Default border / separator color.
muted--maz-muted--maz-muted-50..900Muted / secondary text.
overlay--maz-overlay--maz-overlay-50..900Backdrop / dialog scrim.
shadow--maz-shadow--maz-shadow-50..900Tint used by Tailwind shadow-* utilities (via --color-elevation).

Scales ​

Multi-step scales under scales.<key>.

Preset keyCSS variablesTailwind utility
rounded.xs..3xl--maz-rounded-xs..3xlmaz:rounded-{key} (bridged to --radius-{key})
shadow.{sm, md, lg, xl, elevation}--maz-shadow-style-{key}maz:shadow-{key} (bridged to --shadow-{key})

Same naming-collision avoidance as foundation: rounded.* (instead of radius.*) and shadow-style-* (instead of plain shadow-*) keep the bridge cycle-free under prefix(maz).

Components ​

Per-component knobs under components.<key>. All optional — omit to fall back to the surface tokens.

Preset keyCSS variableNotes
btn.font-weight--maz-btn-font-weightFont-weight on .m-btn. Defaults to '500'.
container.bg.light / .dark--maz-container-bg (per mode)Background of Card, Container, Dialog, Popover, Drawer, … Defaults to var(--maz-surface). Bridged to --color-container.
input.bg.light / .dark--maz-input-bg (per mode)Background of Input, Textarea, Select, Checkbox, … Defaults to var(--maz-surface) light, var(--maz-surface-400) dark. Bridged to --color-input.
dialog.max-width--maz-dialog-max-widthMax-width of MazDialog on tablet and up. Defaults to 38rem (33.25rem on ocean). Overridable via the max-width prop.
dialog.min-width--maz-dialog-min-widthMin-width of MazDialog on tablet and up. Defaults to 32rem (28rem on ocean). Overridable via the min-width prop.

Sample output ​

Each base color is emitted as a light-dark() value; scales --maz-X-50 through --maz-X-950 are derived via OKLCh relative color syntax so they automatically follow base color overrides at runtime, preserving chroma at every step.

css
:root {
  color-scheme: light dark;

  /* Base colors — one declaration each, resolved by the browser */
  --maz-primary: light-dark(oklch(0.65 0.19 254), oklch(0.72 0.18 254));
  --maz-primary-foreground: light-dark(oklch(1 0 0), oklch(0.16 0.01 254));
  --maz-surface: light-dark(oklch(1 0 0), oklch(0.18 0.01 254));
  --maz-foreground: light-dark(oklch(0.26 0.01 254), oklch(0.96 0.005 254));

  /* Auto-generated 50–950 scale — derived via relative color syntax */
  --maz-primary-500: var(--maz-primary);
  --maz-primary-600: oklch(from var(--maz-primary) clamp(0, calc(l - 0.07), 1) c h);
  /* ... */

  /* Foundation */
  --maz-base-font-size: 14px;
  --maz-border-width: 1px;
  --maz-space: 0.25rem;
  --maz-font-family: Manrope, sans-serif, …;
  --maz-motion-normal: 200ms;

  /* Scales */
  --maz-rounded-md: 0.7rem;
  --maz-shadow-style-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), …;
}

/* With darkModeStrategy: 'class' — explicit user choice overrides the system */
.dark  { color-scheme: only dark; }
.light { color-scheme: only light; }

For deeper detail on the generated CSS contract, see the @maz-ui/themes README.

Usage with Nuxt ​

For Nuxt users, check the dedicated Nuxt documentation which covers installation and framework-specific configuration.

Migration from Legacy System ​

If you're using the legacy theme system with CLI:

typescript
// maz-ui.config.ts
export default defineConfig({
  theme: {
    colors: {
      primary: 'hsl(210, 100%, 56%)',
      secondary: 'hsl(164, 76%, 46%)'
    }
  }
})
typescript
// main.ts
import { definePreset, mazUi } from '@maz-ui/themes'
import { MazUiTheme } from '@maz-ui/themes/plugin'

const myTheme = definePreset({
  base: mazUi,
  overrides: {
    colors: {
      light: {
        primary: 'oklch(0.65 0.19 254)',
        secondary: 'oklch(0.72 0.16 168)'
      }
    }
  }
})

app.use(MazUiTheme, { preset: myTheme })

The new system offers much more flexibility and performance!