Reference
API reference
Look up supported class inputs, variant types, defaults, and customization signatures in @hulla/style.
On this page
Look up the inputs accepted by cn(), the types inferred by vn(), and the options for style(). For a worked example, follow Style a button.
import { style } from '@hulla/style'
const { cn, vn } = style()Both configuration properties are optional. serializer defaults to defaultSerializer; composer defaults to defaultComposer. The returned cn and vn share the same configured pipeline. style() does not generate CSS or know framework-specific class props.
ClassName is a non-recursive class input or a conditional object. The source type is ClassNameNonRecursive | Record<string, ClassNameNonRecursive>; ClassNameNonRecursive is an internal helper, not a public export:
import type {
ClassName,
Composer,
Serializer,
StyleConfig,
Variant,
VariantsDefinition,
} from '@hulla/style'ClassName accepts strings, string[], numbers, booleans, nullish values, 0n, and conditional objects. Arrays must contain strings: nested arrays and boolean array members are outside the declared type.
The default serializer produces these values at runtime:
| Input | Serialized output |
|---|---|
'base px-2' | 'base px-2' |
['base', 'px-2'] | 'base px-2' |
{ active: true, hidden: false } | 'active' |
new Set(['base', 'px-2']) | 'base px-2' |
new Map([['base', 'active']]) | 'base active' (entry key and value are serialized) |
'', 0, 0n, NaN, false, null, undefined | '' |
Any non-zero number, such as 42 | Stringified numeric text |
1n | '' (bigint values are not serialized) |
true | '' |
cn(...classes) serializes each argument, then passes the resulting strings to the composer. With the default composer, empty inputs disappear and repeated class names appear only once, in their original order:
cn('rounded px-2', 'px-2 text-sm') // 'rounded px-2 text-sm'
cn(42, NaN) // '42'
cn('px-2', 'px-4') // 'px-2 px-4'Exact-token deduplication is not CSS conflict resolution. defaultComposer keeps both different spacing classes. Configure a utility-aware composer such as twMerge if the later conflicting utility should win; cn itself does not interpret CSS.
vn(variants) maps each key to a class string. Its return type, Variant<V>, is a callable function with an .infer property typed as the keys of your map:
const $size = vn({ compact: 'px-2 text-sm', roomy: ['px-4', 'text-base'] })
type Size = typeof $size.infer // 'compact' | 'roomy'
const classes = $size('compact').infer exists for TypeScript and is undefined at runtime. Call the variant function to get classes.
There is no default selection in vn(). Set a default in your component’s props when callers can omit the choice. Variant classes use the same serializer and composer as cn().
The exported signatures are Serializer = (input: ClassName) => string and Composer = (...strings: string[]) => string; both are synchronous. This runnable example imports the default serializer and changes each input to uppercase before a simple joining composer receives it:
import { defaultSerializer, style } from '@hulla/style'
const { cn } = style({
serializer: (input) => defaultSerializer(input).toUpperCase(),
composer: (...strings) => strings.filter(Boolean).join(' '),
})
cn('base', { active: true }) // 'BASE ACTIVE'A custom serializer receives every cn argument, including strings, and runs on variant input too. Return one string per input position.
A custom composer receives those strings in order and decides how to combine them: preserve duplicates, normalize whitespace, or resolve conflicting utilities. Both hooks are synchronous.
VariantsDefinition describes a variant map, Variant<V> types its callable function and .infer, and StyleConfig holds the two hooks. See Custom merging for a Tailwind example.
The package exports style(config?: StyleConfig), defaultSerializer(input: ClassName): string, defaultComposer(...strings: string[]): string, and the public types ClassName, Serializer, Composer, VariantsDefinition, StyleConfig, and Variant. The Variant<V> shape is ((prop: keyof V) => string) & { infer: keyof V }.