CSS
The question a host arrives with is not “what does this class do” but “do I need the component at all”. A class is markup the server renders; an element is JavaScript that must boot. Where both are possible, the class is usually right, and the element is right for the specific thing only it can do.
| Case | Class, no JavaScript | Element, with behaviour | Reach for |
|---|---|---|---|
| Table | .hui-table on the <table> | hui-table | The class, unless you need row selection, a tri-state header, server-sorted heads or a sticky header that survives a swap |
| Select | .hui-native-select on a native <select> | hui-select | The native select for a plain choice; the element when you want the styled listbox and options the host renders |
| Radio | .hui-radio items | hui-radio-group | Both together: the group is the element, the items are light DOM, and the class is what styles them |
| Accordion | .hui-accordion__trigger / __panel | hui-accordion | Both together: the element wires the behaviour, the classes style the light-DOM children it cannot reach |
| Label | .hui-label on a <label> | hui-field | hui-field when you want the label, description and error wired to the control; the class for a label on its own |
| Text input | A native <input> the host styles | hui-input | The element, unless you need a type it degrades to text, or native semantics that must not cross a shadow boundary |
| Everything else in the sheet | .hui-card, .hui-badge, .hui-alert, … | - | The class; no element exists or is planned |
The cost of each
Section titled “The cost of each”- A class costs nothing at runtime. The server renders it, HTMX swaps it, Alpine binds to it, and it works with JavaScript disabled. Its limit is that it lives in the document, so it cannot hold interaction state or cross a shadow boundary.
- An element costs a module load and an upgrade. In exchange it can own focus, keyboard, ARIA state and a form value, and it can use the top layer. It also creates a shadow boundary the host’s plain CSS cannot cross - which is why its styling is exposed as tokens and
::part()instead.
How this section is arranged
Section titled “How this section is arranged”Components is one page per thing the class sheet ships as a component in its own right - a card, a badge, a breadcrumb. Each page has the markup it expects, because a class with the wrong markup under it is the most common way these go wrong.
Utilities is what is left once the components are accounted for: the prose rules and the page surface. They are not components, they carry no markup contract, and they are kept apart so the component list is not diluted by them.
Classes that exist only to dress markup inside a custom element - .hui-sidebar-item,
.hui-accordion__trigger, .hui-table__sort, .hui-tree__marker - are not here. They are
documented on that element’s own page under Components, because they are part of its API rather
than something you would reach for on their own.
The elements themselves each have their own page under Components.