cx
StableJoins class names and skips anything falsy, so each condition sits next to the classes it adds.
The two-line helper nearly every component in the Library uses. It takes strings, false, null and undefined, and joins what is left with spaces. It doesn’t accept objects or arrays and doesn’t merge Tailwind classes that conflict: when both px-2 and px-4 end up in the list, the stylesheet decides, so pick one with a ternary.
Updated View source
Classes from state
import { useState } from 'react'import { cx } from '@rchursin/components/cx'import { Icon } from '@rchursin/product/icons/Icon'import { Switch } from '@rchursin/product/ui'
/** * cx joins class names and drops anything falsy, so each condition sits next to the classes it * adds. Flip the switches: the row and the class string below it change together. */export default function CxConditional() { const [selected, setSelected] = useState(true) const [done, setDone] = useState(false) const [compact, setCompact] = useState(false)
const className = cx( 'flex items-center gap-3 rounded-xl px-3 text-[13px] ring-1 ring-inset', compact ? 'h-9' : 'h-12', selected ? 'bg-selected ring-line-strong' : 'ring-line', done && 'text-fg-3 line-through', )
return ( <div className="flex w-full max-w-md flex-col gap-5"> <div className="flex flex-col gap-3"> <Switch checked={selected} onChange={setSelected} label="Selected" /> <Switch checked={done} onChange={setDone} label="Done" /> <Switch checked={compact} onChange={setCompact} label="Compact" /> </div> <div className={className}> <Icon name={done ? 'status-done' : 'status-todo'} className="size-4 shrink-0" /> <span className="min-w-0 truncate">Rotate the staging API keys</span> </div> <code className="rounded-lg bg-sunken px-3 py-2.5 font-mono text-[11.5px]/5 break-words text-fg-2">{className}</code> </div> )}Usage
import { cx } from '@rchursin/components/cx'<div className={cx('rounded-xl px-3', selected && 'bg-selected', compact ? 'h-9' : 'h-12', className)} />API
cx(...classes)
| Prop | Type | Default | Description |
|---|---|---|---|
...classesrequired | Array<string | false | null | undefined> | – | Class strings and conditions. Falsy values (false, null, undefined, "") are dropped. |
returns | string | – | The rest, joined with single spaces. No duplicates are removed. |
Guidelines
Do
- Put the base classes first and the conditional ones after.
- Use a ternary when two classes compete (h-9 or h-12), so only one is ever in the list.
- Take a className prop last, so callers can add to it.
Don’t
- Don’t pass a number as the condition: cx(count && "x") doesn’t compile; write count > 0 && "x".
- Don’t rely on order to override a Tailwind class; the stylesheet’s order wins, not the string’s.
In use
Related
- ColourTwelve semantic greys that switch with the theme, and six fixed brand values for the logo and brand panels.
- FocusA 2px ring in the text colour, outside or inside the edge, plus the lit-metal buttons’ own ring. Keyboard only.
- Cards & elevationCards on one four-step elevation scale: lux-card, a lower lux-card-flat, lux-lift on hover and lux-float for overlays.
Source
The component’s own files, exactly as they run on this site.
/** Join class names, skipping falsy values. */export function cx(...classes: Array<string | false | null | undefined>): string { return classes.filter(Boolean).join(" ")}