icon

Morainev0.5.0

Styling

Styling

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

View as Markdown

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:

<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 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 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.

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.

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#

@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:

@import 'moraine/icon.css';

v3#

/** @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
],
}
@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#

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.
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.

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.

import { extendCN } from 'moraine'
import { twMerge } from 'tailwind-merge'
extendCN(twMerge)