ClassPropsinterface
{
readonly className?: Classes | undefined;
}| Property | Type | |
|---|---|---|
.className | Classes | Set of classnames that can be joined into a single className string. readonly |
Props for a component that accepts an additional className, merged after the classes the component computes for itself.
- Accepts anything
Classesallows — a string, a nested array of strings, or aVariantsdictionary oftrueflags. - Merged last so an app's own (unlayered) stylesheet reliably beats the library's
@layer componentsrules. - Component hooks (
--card-backgroundetc.) and the tint ladder remain the primary theming surface;classNameis the escape hatch for one-off treatments they can't express.
The shared className prop most components accept — an app class merged after the classes the component computes for itself.
Things to know:
- CSS-module class names are hashed, so an app stylesheet can't target a component instance on its own.
classNameis the sanctioned way to attach an app class to a specific instance. - It's merged last, and an app's global stylesheet is unlayered, so its rules beat the library's
@layer componentsrules without a specificity fight. - It accepts any
Classesvalue — a string, a nested array, or a dictionary whosetruekeys become class names — because it goes throughgetClass()like every other class the component composes. - It lands on the component's own root element. A component that renders more than one element (
<Card>'s stretched overlay link,<Field>'s inner input) keeps the class on the outer one.<Popover>is the one deliberate exception: its class goes on the floating panel, not the wrapper that anchors it. - Not every component takes one. Formatting-only components (
<When>,<Prose>,<Scroll>) exist to format what's already there, the*Layoutcomponents are structural (write a new layout rather than bending an existing one), and<Dialog>is nearly always rendered from the dialogs state object. Style the content inside them instead. - Tokens and per-property hooks (
--card-background,--button-radius, …) remain the primary theming surface. Reach forclassNamefor a composed one-off treatment those can't express — a keyframe animation, a bespoke gradient, a transform on press — not to restyle a component everywhere.
Usage
Attaching an app class
import { Button } from "shelving/ui";
// `.spongy-press` lives in the app's own global stylesheet.
<Button className="spongy-press" onClick={claim}>
Claim
</Button>;/* style.css — unlayered, so it beats the library's `@layer components` rules. */
.spongy-press:active {
transform: scale(0.96);
}Conditional classes
import { Card } from "shelving/ui";
// Arrays and `true`-flag dictionaries work too — anything `Classes` allows.
<Card className={["claim-card", { sparkle: isNew }]} href={path}>
{content}
</Card>;Adding it to your own component
import { type ClassProps, getClass } from "shelving/ui";
export interface AddressProps extends BlockVariants, ChildProps, ClassProps {}
export function Address({ children, className, ...props }: AddressProps): ReactElement {
// Merge `className` last so a caller's class wins.
return <address className={getClass(ADDRESS_CLASS, getBlockClass(props), className)}>{children}</address>;
}