---
title: Styling
description: Configure Moraine design tokens and component styles with UnoCSS or Tailwind CSS.
sidebar:
  order: 2
search:
  tags: [UnoCSS, Tailwind CSS, theme, tokens]
---

# Styling

> Configure Moraine design tokens and component styles with UnoCSS or Tailwind CSS.

## Interactive State Color Tokens

Moraine uses semantic `hover` and `active` color tokens for interactive surfaces instead of applying translucent overlays. Defining opaque colors for each state keeps contrast predictable when controls appear on different backgrounds.

State tokens are available for `background`, `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, and `destructive`. Use them through the same color utilities as the base token:

```tsx
<button class="bg-primary hover:bg-primary-hover active:bg-primary-active text-primary-foreground">
  Continue
</button>
```

Each state has a built-in fallback, so themes can adopt the tokens incrementally:

| Utility             | CSS variable fallback                                         |
| ------------------- | ------------------------------------------------------------- |
| `bg-primary-hover`  | `var(--primary-hover, var(--primary))`                        |
| `bg-primary-active` | `var(--primary-active, var(--primary-hover, var(--primary)))` |

The same fallback applies to every supported color family: `hover` falls back to the base color, while `active` falls back to `hover` and then the base color. There is no separate `focus` color token; focused or keyboard-highlighted interactions reuse the hover color where applicable, while focus visibility remains controlled by ring and border styles.

You can define the variables directly in CSS or generate them with UnoCSS's [`colorVariables`](#color-variables) option. Moraine's Tailwind plugin exposes the same utility names.

## UnoCSS

Use either `presetWind3` or `presetWind4`, then add `presetMoraine`. Built-in component animations are included in the preset, so no extra animation preset is needed.

[`@subf/unocss`](https://github.com/subframe7536/unocss) is a subset of UnoCSS that optimizes for `solid-js` + `oxlint`'s use case. It is recommended for use with Moraine, but the full `unocss` can also be used if desired.

```tsx
// unocss.config.ts
import { presetWind3, presetWind4 } from '@subf/unocss'
import { presetMoraine } from 'moraine/unocss'

export default defineConfig({
  presets: [
    // presetWind3(),
    presetWind4(),
    presetMoraine({
      enableComponentLayer: true,
    }),
    // ...other presets
  ],
})
```

### Color variables

The preset can generate light and dark semantic variables from the same grouped shape used by Moraine's color utilities. Nothing is generated unless `colorVariables` is configured. Light and dark selectors default to `:root` and `.dark`.

```ts
presetMoraine({
  colorVariables: {
    light: {
      foreground: 'oklch(20% 0 0)',
      primary: {
        DEFAULT: 'oklch(55% 0.2 260)',
        foreground: 'white',
        hover: 8,
        active: ({ base, foreground }) => `color-mix(in oklch, ${base}, ${foreground} 16%)`,
      },
      border: 'oklch(90% 0 0)',
    },
    dark: {
      foreground: 'oklch(96% 0 0)',
      primary: {
        DEFAULT: 'oklch(70% 0.16 260)',
        foreground: 'oklch(18% 0 0)',
      },
      border: 'oklch(32% 0 0)',
    },
    hoverAdjustment: 6,
    activeAdjustment: 12,
    // lightSelector: ':root',
    // darkSelector: '.dark',
  },
})
```

Exact state colors override the global adjustments. Numeric state values generate an opaque `color-mix()` against the group's foreground color; missing state variables continue to fall back through Moraine's existing utility tokens.

## Tailwind CSS

Moraine ships a first-class Tailwind plugin that injects CSS variable tokens, theme extensions (colors, font families, radius, shadows, keyframes, animations, animation metadata), and `icon-*` utility stubs. It also provides `data-*` and `aria-*` variants for attribute-based styling.

### v4

```css
@import 'tailwindcss';

/* Moraine plugin: tokens, theme, icon stubs */
@plugin 'moraine/tailwind';

/* Optional: on-demand icon utilities (recommended) */
@plugin '@iconify/tailwind' {
  collections: lucide;
}

/* Scan moraine dist for component utility classes */
@source './node_modules/moraine/**/*';
```

Or, for a zero-config icon setup (larger bundle), import the pre-built icon CSS instead:

```css
@import 'moraine/icon.css';
```

### v3

```js
/** @type {import('tailwindcss').Config} */
const { addIconSelectors } = require('@iconify/tailwind')
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}', './node_modules/moraine/dist/**/*'],
  plugins: [
    require('moraine/tailwind')(),
    addIconSelectors(['lucide']), // optional, for on-demand icon utilities
  ],
}
```

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### Icon tiers

| Tier                        | How                                                   | Size              |
| --------------------------- | ----------------------------------------------------- | ----------------- |
| **1 — `moraine/icon.css`**  | `@import 'moraine/icon.css'` — zero deps              | ≈ full lucide set |
| **2 — `@iconify/tailwind`** | Install `@iconify/tailwind`, use per the config above | On-demand ✓       |

### Plugin options

```ts
import { moraineTailwind } from 'moraine/tailwind'

moraineTailwind({
  icons: true, // emit icon-* utility stubs (default: true)
})
```

## Override Component Styles

Use top-level `class` and `style` for the component root in normal components. Use `classes` and `styles` for named slots when you need to target a specific inner slot or overlay content; keys match slot names.

- Overlay components apply top-level `class` and `style` to their **trigger** slot, because the floating content is rendered separately.

```tsx
import { Button } from 'moraine'

function MyButton() {
  return (
    <Button class="bg-red-500" style={{ borderColor: 'red' }} classes={{ label: 'bg-green-500' }}>
      Click me
    </Button>
  )
}
```

## Class Concatenation

Moraine provides `cn()` and `cva()` utilities that concatenate class strings and conditionals. It is used internally to merge user-provided classes with defaults.

```ts
import { cn, cva } from 'moraine'

cn('btn', 'btn-primary', isDisabled && 'btn-disabled') // => "btn btn-primary btn-disabled"

const buttonClasses = cva('btn', {
  variants: {
    color: {
      primary: 'btn-primary',
      secondary: 'btn-secondary',
    },
    disabled: {
      true: 'btn-disabled',
    },
  },
})
// Reuse `cn()` under the hood to merge variant classes with other conditionals
buttonClasses({ color: 'primary', disabled: isDisabled }, 'other', condition && 'another')
// => "btn btn-primary btn-disabled other another"
```

## Patch Built-in `cn`

Moraine exports `extendCN` so you can plug in class merge utilities like `tailwind-merge`.

```tsx
import { extendCN } from 'moraine'
import { twMerge } from 'tailwind-merge'

extendCN(twMerge)
```

