---
title: "Design System"
description: "Pohon UI's design system uses UnoCSS for simple theming and easy customization."
canonical_url: "https://vinicunca.dev/pohon/getting-started/theme/design-system"
---
# 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.

```css [app/assets/css/main.css]
:root {
  /* Your custom design tokens go here */
}
```

> [!NOTE]
> See: https://unocss.dev/presets/wind4#theme
> 
> Check the UnoCSS documentation for all available theme variable customization options.

### Fonts

Use the `--font-*` theme variables to [customize the font family utilities](https://unocss.dev/presets/wind4#theme) in your project.

```css [app/assets/css/main.css]
:root {
  --font-sans: 'Public Sans', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
}
```

**Nuxt:**

> [!NOTE]
> See: /pohon/getting-started/integrations/fonts
> 
> 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](https://unocss.dev/config/theme). Keep `pohonColors` in the object so the semantic utilities stay available:

```ts [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](#semantic-colors) instead.

> [!TIP]
> 
> 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:
> 
> ```ts [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](https://unocss.dev/config/theme).

```ts [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](https://unocss.dev/presets/wind4#theme) of the animations and movement transitions in your project.

```css [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.

> [!NOTE]
> 
> 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](#configure-colors), so components never depend on a specific palette.

| Color | Example palette | Description |
| --- | --- | --- |
| `primary` | `green` | Main CTAs, active navigation, brand elements, important links |
| `secondary` | `blue` | Secondary buttons, alternative actions, complementary UI elements |
| `success` | `green` | Success messages, completed states, positive confirmations |
| `info` | `blue` | Info alerts, tooltips, help text, neutral notifications |
| `warning` | `yellow` | Warning messages, pending states, attention-needed items |
| `error` | `red` | Error messages, validation errors, destructive actions |
| `neutral` | `slate` | Text, borders, backgrounds, disabled states |

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

```vue
<template>
  <PButton color="primary">
    Save Changes
  </PButton>
</template>
```

> [!NOTE]
> 
> Try the  theme menu in the header to instantly see how different color schemes affect the entire UI, or open the [theme editor](https://vinicunca.dev/theme) 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:

```css [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](https://vinicunca.dev/pohon/getting-started/theme/css-variables#foreground) for text placed on top of it.

`neutral` works the same way: define the `--ui-color-neutral-*` shades and the [text](https://vinicunca.dev/pohon/getting-started/theme/css-variables#text), [background](https://vinicunca.dev/pohon/getting-started/theme/css-variables#background) and [border](https://vinicunca.dev/pohon/getting-started/theme/css-variables#border) variables that read them.

> [!WARNING]
> 
> 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.

> [!NOTE]
> 
> 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:

**Nuxt:**

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

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

**Vue:**

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

```ts [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](#configure-colors):

```css [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:

```ts [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](https://vinicunca.dev/pohon/getting-started/theme/css-variables):

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


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
