Design System

Pohon UI's design system uses UnoCSS for simple theming and easy customization.

UnoCSS

Configure generated utilities in uno.config.ts with presetVinicunca(). Pohon UI also reads CSS variables for runtime color shades and other design tokens.

app/assets/css/main.css
:root {
  /* Your custom design tokens go here */
}
Check the UnoCSS documentation for all available theme variable customization options.

Fonts

Use the --font-* theme variables to customize the font family utilities in your project.

app/assets/css/main.css
:root {
  --font-sans: 'Public Sans', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
}
Fonts defined here are automatically loaded and optimized by the @nuxt/fonts module.

Colors

Add or override palette colors under the theme.colors key of your UnoCSS configuration. Keep pohonColors in the object so the semantic utilities stay available:

uno.config.ts
import { presetVinicunca } from '@vinicunca/unocss-preset';
import { pohonColors } from 'pohon-ui/unocss';
import { defineConfig } from 'unocss';

export default defineConfig({
  presets: [
    presetVinicunca()
  ],
  theme: {
    colors: {
      ...pohonColors,
      // Override default green color
      green: {
        50: '#effdf5',
        100: '#d9fbe8',
        200: '#b3f5d1',
        300: '#75edae',
        400: '#00dc82',
        500: '#00c16a',
        600: '#00a155',
        700: '#007f45',
        800: '#016538',
        900: '#0a5331',
        950: '#052e16'
      },
      // Define new custom color
      brand: {
        50: '#fef2f2',
        100: '#fee2e2',
        200: '#fecaca',
        300: '#fca5a5',
        400: '#f87171',
        500: '#ef4444',
        600: '#dc2626',
        700: '#b91c1c',
        800: '#991b1b',
        900: '#7f1d1d',
        950: '#450a0a'
      }
    }
  }
});

This gives you utilities like bg-brand-500 or color-green-400. It doesn't change the color of Pohon UI components, which read the semantic colors instead.

The spread only merges the top level, so overriding one key of a pohonColors entry would drop the rest of that entry. Use mergeDeep from unocss to change a single value and keep its siblings:
uno.config.ts
import { presetVinicunca } from '@vinicunca/unocss-preset';
import { pohonColors } from 'pohon-ui/unocss';
import { defineConfig, mergeDeep } from 'unocss';

export default defineConfig({
  presets: [
    presetVinicunca()
  ],
  theme: {
    // `primary` keeps its shades, only `color-primary-foreground` changes
    colors: mergeDeep(pohonColors, {
      primary: { foreground: 'white' }
    })
  }
});

Breakpoints

Configure responsive breakpoints in the UnoCSS theme.

uno.config.ts
import { defineConfig } from 'unocss';

export default defineConfig({
  theme: {
    breakpoints: { '3xl': '1920px', '4xl': '2560px', '5xl': '3840px' }
  }
});

Motion

Use the --ease-* theme variables to customize the easing of the animations and movement transitions in your project.

app/assets/css/main.css
:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

Enter and exit animations run on --ease-out, indeterminate loading animations on --ease-in-out, so overriding these two retimes the library.

When prefers-reduced-motion is set, overlays fade in place instead of scaling or sliding and movement transitions are disabled.

Color System

Pohon UI's color system is designed around semantic naming rather than specific color values. This approach makes your UI more maintainable and makes theme switching easy.

Semantic colors

Pohon UI provides semantic color aliases that describe the purpose of the color. Each alias reads its shades from --ui-color-<alias>-* CSS variables that you define in your CSS, so components never depend on a specific palette.

ColorExample paletteDescription
primarygreenMain CTAs, active navigation, brand elements, important links
secondaryblueSecondary buttons, alternative actions, complementary UI elements
successgreenSuccess messages, completed states, positive confirmations
infoblueInfo alerts, tooltips, help text, neutral notifications
warningyellowWarning messages, pending states, attention-needed items
errorredError messages, validation errors, destructive actions
neutralslateText, borders, backgrounds, disabled states

These semantic colors are available in the color prop of Pohon UI components:

<template>
  <PButton color="primary">Save Changes</PButton>
</template>
Try the theme menu in the header to instantly see how different color schemes affect the entire UI, or open the theme editor to customize everything at once!

Configure colors

Define the shades of each semantic color in your main.css file, from 50 to 950, then pick the shade it uses in light and dark mode:

app/assets/css/main.css
:root {
  --ui-color-primary-50: oklch(98.2% 0.018 155.826);
  --ui-color-primary-100: oklch(96.2% 0.044 156.743);
  --ui-color-primary-200: oklch(92.5% 0.084 155.995);
  --ui-color-primary-300: oklch(87.1% 0.15 154.449);
  --ui-color-primary-400: oklch(79.2% 0.209 151.711);
  --ui-color-primary-500: oklch(72.3% 0.219 149.579);
  --ui-color-primary-600: oklch(62.7% 0.194 149.214);
  --ui-color-primary-700: oklch(52.7% 0.154 150.069);
  --ui-color-primary-800: oklch(44.8% 0.119 151.328);
  --ui-color-primary-900: oklch(39.3% 0.095 152.535);
  --ui-color-primary-950: oklch(26.6% 0.065 152.934);
}

:root,
.light {
  --ui-color-primary: var(--ui-color-primary-500);
}

.dark {
  --ui-color-primary: var(--ui-color-primary-400);
}

Repeat these variables for secondary, success, info, warning and error. Each color can also take a foreground for text placed on top of it.

neutral works the same way: define the --ui-color-neutral-* shades and the text, background and border variables that read them.

Define all 11 shades for each color. Component themes use more than the base shade, such as 300 and 600.

Since these are plain CSS variables, you can switch palettes at runtime by declaring another set under a selector like [data-theme="blue"] and toggling it on the <html> element.

Pohon UI does not generates these variables from ui.colors in your app config. Setting primary: 'blue' there has no effect on colors, so define the variables in your CSS instead.

Extend colors

You may want to define extra semantic colors beyond the defaults, such as adding a tertiary color:

First, register the new color in your nuxt.config.ts under the ui.theme.colors key:

nuxt.config.ts
export default defineNuxtConfig({
  ui: {
    theme: {
      colors: [
        'primary',
        'secondary',
        'tertiary',
        'info',
        'success',
        'warning',
        'error'
      ]
    }
  }
});

First, register the new color in your vite.config.ts file under the theme.colors key:

vite.config.ts
import vue from '@vitejs/plugin-vue';
import ui from 'pohon-ui/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    vue(),
    ui({
      theme: {
        colors: [
          'primary',
          'secondary',
          'tertiary',
          'info',
          'success',
          'warning',
          'error'
        ]
      }
    })
  ]
});

Then, define its shades in your main.css file, the same way as the other colors:

app/assets/css/main.css
:root {
  --ui-color-tertiary-50: oklch(96.2% 0.018 272.314);
  /* ...100 to 900 */
  --ui-color-tertiary-950: oklch(25.7% 0.09 281.288);
}

:root,
.light {
  --ui-color-tertiary: var(--ui-color-tertiary-500);
}

.dark {
  --ui-color-tertiary: var(--ui-color-tertiary-400);
}

Next, add its utilities to your UnoCSS configuration, since pohonColors only covers the default colors:

uno.config.ts
import { presetVinicunca } from '@vinicunca/unocss-preset';
import { pohonColors } from 'pohon-ui/unocss';
import { defineConfig } from 'unocss';

const shades = [
  '50',
  '100',
  '200',
  '300',
  '400',
  '500',
  '600',
  '700',
  '800',
  '900',
  '950'
];

export default defineConfig({
  presets: [
    presetVinicunca()
  ],
  theme: {
    colors: {
      ...pohonColors,
      tertiary: {
        DEFAULT: 'var(--ui-color-tertiary)',
        foreground: 'var(--ui-color-tertiary-foreground)',
        ...Object.fromEntries(shades.map((shade) => [shade, `var(--ui-color-tertiary-${shade})`]))
      }
    }
  }
});

Finally, use this new color in components that support the color prop or as a class:

<PButton color="tertiary">
  Special Action
</PButton>