‮Skip to content‬
uiify

‮Styling‬

‮Components ship semantic markup, a documented state contract and no visual styles. Style them with plain CSS, Tailwind or the optional‬ @gunkin/uiify/styles ‮layer.‬

‮The styling contract‬

‮Every public part accepts your‬ className, style, ref‮, event handlers, native props and‬ data-* ‮attributes directly. There are no parallel props such as‬ contentClassName ‮or‬ triggerClassName.

‮uiify owns the identity and state attributes. They are styling hooks: change state through props or native HTML behavior, never by writing‬ data-state‮. The‬ --uiify-* ‮namespace is reserved for geometry, animation and per-instance values.‬

<Popover.Root>
  <Popover.Trigger className="toolbar-trigger">Open</Popover.Trigger>
  <Popover.Content className="toolbar-popover" data-layout="compact">
    Actions
  </Popover.Content>
</Popover.Root>

‮Naming‬

‮Hook‬‮Purpose‬‮Example‬
‮Identity markers‬‮Stable component identity, owned by uiify‬data-uiify-popover
‮Anatomy markers‬‮Public compound part, owned by uiify‬data-part="content"
‮State attributes‬‮Read-only state mirror for selectors‬data-state="open"
‮Custom properties‬‮Dynamic geometry and per-instance values‬--uiify-anchor-x

‮Each state-bearing root has one presence-valued‬ data-uiify-<component> ‮marker. Each compound part has one kebab-case‬ data-part ‮value, such as‬ trigger, content, item ‮or‬ label‮. Consumer props cannot override these values.‬

‮Plain CSS and Tailwind‬

[data-uiify-popover][data-part="content"] {
  min-inline-size: 12rem;
}

[data-uiify-popover][data-part="content"][data-state="open"] {
  opacity: 1;
}

/* Tailwind uses the same attributes. */
<div className="data-[state=open]:opacity-100" />

‮Polymorphism‬

as ‮and‬ render ‮replace the rendered element. A custom renderer must forward the props and ref it receives to the DOM element, or markers, state and styling stop working.‬

‮behavior.css is required‬

‮This stylesheet holds the rules that make state work: popover positioning,‬ ::details-content ‮sizing,‬ :has() ‮propagation, hidden-input placement and‬ @starting-style ‮transitions. It has no colors, fonts or spacing.‬

import "@gunkin/uiify/components/behavior.css";

‮Without it, components render but their state does not work. Import it once, anywhere in your app.‬

‮State is an attribute, not a class‬

‮Components expose state as‬ data-* ‮attributes on the element that owns it, so CSS can select it without JavaScript or class names. Each component page lists the attributes it emits.‬

.my-trigger[data-state="open"] { … }
.my-item[data-disabled] { … }

/* Native state works the same way, because the state IS native */
.my-panel:popover-open { … }
details[open] > summary { … }

‮Variants are‬ data-* ‮attributes too, not container style queries, because a container cannot query itself.‬

‮Values inline, rules in a stylesheet‬

‮Per-instance values arrive as inline custom properties; every rule lives in a static stylesheet. Inline styles cannot express pseudo-classes, pseudo-elements, container queries or‬ @starting-style‮, and the state model depends on all of them.‬

‮Inline properties stream with the HTML without hydration mismatches, and one stylesheet is parsed once and cached. CSS-in-JS instead injects‬ <style> ‮chunks into every boundary and does not cache across requests.‬

‮Three ways to style‬

‮Your own CSS.‬ ‮Select the element and its‬ data-* ‮attributes; there are no default styles to override.‬

‮Tailwind.‬ ‮The same attributes work as variants, for example‬ data-[state=open]:rotate-180‮. Site previews switch to the Tailwind skin from the Style setting in the header.‬

@gunkin/uiify/styles. ‮An optional plain-CSS layer with OKLCH token scales, light and dark themes and a skin for every component — a starting point, not a requirement.‬

import "@gunkin/uiify/styles/reset";
import "@gunkin/uiify/styles";

‮Cascade order‬

‮Everything uiify ships sits in a named layer below yours:‬

@layer ui.reset, ui.behavior, ui.tokens, ui.theme, app;

‮Your layer always wins: overrides need no‬ !important ‮or specificity tricks, and they survive library refactors.‬

‮Each‬ ‮component page‬ ‮lists its attributes and CSS variables, with copy-paste CSS for both skins.‬