Skip to content

Theming

Modern and performant theme system for Maz-UI with TypeScript, HSL CSS variables and flexible strategies.

@maz-ui/themes

Features

  • Modern HSL CSS Variables - Maximum flexibility with colors
  • Smart Dark Mode - Automatic support with prefers-color-scheme
  • Automatic Generation - Color scales (50-950) created automatically
  • Performance Strategies - Runtime, build-time or hybrid according to your needs
  • Strict TypeScript - Complete types for perfect DX
  • Zero FOUC - Critical CSS injected inline to avoid flashes
  • Flexible Presets - Ready-to-use and customizable configurations

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
    strategy: 'hybrid', // 'runtime' | 'buildtime' | 'hybrid'
    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')
  }
})

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: hsl(var(--maz-background));
  color: hsl(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-background maz-text-foreground">
    <button
      class="maz-rounded 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): The rendering strategy to use
  • darkModeStrategy (optional): The dark mode strategy to use, only if you use mode both
  • mode (optional): The supported color modes to use (light, dark, both)
  • colorMode (optional): The initial color mode to use (only if mode is 'both')

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'

Rendering Strategies

The hybrid strategy combines the best of both worlds:

  • Critical CSS injected immediately - Prevents FOUC (Flash of Unstyled Content)
  • Full CSS loaded asynchronously - Uses requestIdleCallback to avoid blocking the main thread
  • Optimal performance - Balance between display speed and interface fluidity

The full CSS is injected via requestIdleCallback with a 100ms timeout, allowing the browser to prioritize critical tasks while ensuring fast loading of complete styling.

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

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

⚡ Runtime

CSS generated (critical and full) injected immediately.

⚠️ Potential risks:

  • Main thread blocking - Immediate injection can impact performance
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: {
      'radius': '0.75rem',
      'border-width': '2px',
      'font-family': 'Inter, sans-serif'
    },
    colors: {
      light: {
        primary: '210 100% 50%',
        secondary: '210 40% 96%',
        background: '210 20% 98%',
        accent: '280 100% 70%'
      },
      dark: {
        primary: '210 100% 60%',
        secondary: '210 40% 15%',
        background: '210 20% 8%',
        accent: '280 100% 80%'
      }
    }
  }
})

useTheme Composable API

typescript
const {
  // Reactive state
  presetName, // ComputedRef<string>
  colorMode, // ComputedRef<'light' | 'dark' | 'auto'>
  isDark, // ComputedRef<boolean>
  strategy, // ComputedRef<'runtime' | 'buildtime' | 'hybrid'>

  // Actions
  updateTheme, // (preset: ThemePreset | ThemePresetOverrides) => void
  setColorMode, // (mode: 'light' | 'dark' | 'auto') => void
  toggleDarkMode // () => void
} = useTheme()

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: '221.2 83.2% 53.3%' },
      dark: { primary: '217.2 91.2% 59.8%' },
    },
  },
})

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

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

/**
 * Or generate theme in  separate CSS files
 */
const { critical, full } = buildSeparateThemeFiles(customPreset, {
  darkSelector: 'class',
})

writeFileSync(join(_dirname, 'public/critical.css'), critical)
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_IDS,
} from '@maz-ui/themes'

// Custom theme
const customPreset = definePreset({
  base: mazUi,
  overrides: {
    name: 'custom',
    colors: {
      light: { primary: '221.2 83.2% 53.3%' },
      dark: { primary: '217.2 91.2% 59.8%' },
    },
  },
})

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

// Or generate separate CSS files
const { critical, 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_IDS.CRITICAL, type: 'text/css' }, critical],
    ['style', { id: CSS_IDS.FULL, 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: '221.2 83.2% 53.3%' },
      dark: { primary: '217.2 91.2% 59.8%' },
    },
  },
})

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
<!-- Critical CSS first -->
<link rel="stylesheet" href="/themes/custom-critical.css">

<!-- Full CSS deferred -->
<link rel="stylesheet" href="/themes/custom-full.css" media="print" onload="this.media='all'">

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'
  criticalOnly: false, // true for critical CSS only
})

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: { critical, full, lightOnly, darkOnly }

createThemeStylesheet(css, options)

Creates a <style> 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

Generated CSS Variables

The system automatically generates all necessary variables:

Base Variables

css
:root {
  /* Main colors */
  --maz-primary: 210 100% 56%;
  --maz-primary-foreground: 0 0% 100%;
  --maz-secondary: 164 76% 46%;
  --maz-background: 0 0% 100%;
  --maz-foreground: 210 8% 14%;

  /* Design tokens */
  --maz-radius: 0.7rem;
  --maz-border-width: 0.063rem;
  --maz-font-family: Manrope, sans-serif;
}

Automatic Color Scales

css
:root {
  /* Primary scale generated automatically */
  --maz-primary-50: 210 100% 95%;
  --maz-primary-100: 210 100% 87%;
  --maz-primary-200: 210 100% 79%;
  /* ... up to 900 */
  --maz-primary-900: 210 79% 17%;
}

Dark Mode

css
.dark {
  --maz-background: 235 16% 15%;
  --maz-foreground: 0 0% 85%;
  /* Variables automatically adapted */
}

/* Or with media query */
@media (prefers-color-scheme: dark) {
  :root {
    --maz-background: 235 16% 15%;
    --maz-foreground: 0 0% 85%;
  }
}

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'

const myTheme = definePreset({
  base: mazUi,
  overrides: {
    colors: {
      light: {
        primary: '210 100% 56%',
        secondary: '164 76% 46%'
      }
    }
  }
})

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

The new system offers much more flexibility and performance!