Going Headless
GolemUI’s headless mode gives you full control over the look and feel of every widget. The widget engine — accessibility, focus management, validation, state, events — keeps working without any styles. You write 100% of the CSS, targeting the BEM class names every widget emits.
This page is the build-from-scratch guide. If you only need to tweak a few colors or fonts, see Customization instead — overriding CSS variables is far less work.
Live demo
Section titled “Live demo”The form below is rendered through the headless playground app — no courtesy stylesheet is loaded; every visual is hand-written CSS targeting the BEM class names that the widgets emit.
When to go headless
Section titled “When to go headless”Pick headless mode when:
- Your design system is completely different from the default — different spacing rhythm, different typography stack, different border-radius philosophy.
- You’re embedding GolemUI inside a host design system (Material, Bootstrap, Tailwind UI, your in-house tokens) and you don’t want the courtesy CSS competing with it.
- You need to drop the bundle weight of
@golemui/gui-components/index.cssfrom the critical path. - You’re building a print or email render of a form where the courtesy layer’s interactive states aren’t relevant.
Skip headless mode when you only want to adjust the default look — switching colors, tweaking the radius, swapping the font. Override CSS variables instead. See Customization / CSS variable overrides.
Step-by-step
Section titled “Step-by-step”-
Skip the courtesy stylesheet
Section titled “Skip the courtesy stylesheet”Headless mode is opt-out, not opt-in: simply don’t import
@golemui/gui-components/index.cssanywhere in your project. With no import, no widget styles are loaded — the engine still wires every interaction, but every element renders with browser-default visuals.@import '@golemui/gui-components/index.css'; -
Read the widget anatomy you’ll style against
Section titled “Read the widget anatomy you’ll style against”Every widget reference page ends with an Anatomy section that shows the rendered HTML — the exact markup the widget emits. Treat each anatomy block as the contract for your CSS:
- Textinput —
.gui-widget,<input type="text" />,.gui-widget__hint,.gui-widget__error. - Password — adds
.gui-password__togglefor the show/hide button. - Dropdown —
.gui-dropdown,.gui-dropdown__list,.gui-dropdown__item, item-renderer slot. - Toggle —
.gui-toggle,.gui-toggle--slider. - Repeater —
.gui-repeater,.gui-repeater__card,.gui-repeater__add-btn. - Flex / Grid —
.gui-flex__widget,.gui-grid__widget.
The class-naming convention is BEM:
.gui-<block>/.gui-<block>__<element>/.gui-<block>--<modifier>. See Customization / Class-based overrides / Naming convention for the full pattern. - Textinput —
-
Decide how interactive state surfaces visually
Section titled “Decide how interactive state surfaces visually”The engine sets these standard attributes on every input — your CSS reads them to express focus, validity, and disabled state:
Selector Meaning :focusThe control has keyboard focus. :hoverThe pointer is over the control. [aria-invalid='true']Validation has failed for this input. :disabled/[disabled]The widget is disabled ( disabled: trueor state).[aria-readonly='true']The widget is in readonly mode. [aria-checked='true']A toggle / checkbox is on. .gui-widget__errorThe error-message element rendered under invalid inputs. .gui-widget__hintThe hint-text element rendered under inputs that declare a hint. -
Lay down the form scaffolding
Section titled “Lay down the form scaffolding”Start with the form-level wrapper and the layout containers (flex/grid). Every widget the engine renders is wrapped in a
.gui-widgetelement, so a single rule covers the per-field spacing.@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');.gui-form {font-family: 'Roboto', sans-serif;font-optical-sizing: auto;font-weight: 400;form {margin: 12px;}}/* Vertical spacing inside flex layouts */.gui-flex__widget {display: flex;flex-direction: column;gap: 16px;}/* Inputs are inline so labels can sit beside the trigger */.gui-widget {display: flex;align-items: center;} -
Style the input controls
Section titled “Style the input controls”Target the native HTML element inside
.gui-widget. The same rule covers textinput, password, etc. — read[type='...']if you need per-type tweaks..gui-widget {input {width: 100%;&[type='text'],&[type='password'] {font-size: 16px;padding: 12px 16px;border: 2px solid #e2e8f0;border-radius: 8px;background: white;color: #1e293b;transition: border-color 0.15s ease;&:focus {border-color: #3b82f6;outline: none;box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);}}&[aria-invalid='true'] {border-color: #ef4444;}&:disabled {background: #f1f5f9;color: #94a3b8;cursor: not-allowed;}}} -
Handle widget-specific affordances
Section titled “Handle widget-specific affordances”Some widgets ship extra controls inside
.gui-widget. The password widget, for example, renders a show/hide toggle:.gui-widget {.gui-password__toggle {position: absolute;inset-inline-end: 20px;background: transparent;border: none;outline: none;cursor: pointer;}}The repeater renders one card per row, an add button, and a remove button per card:
.gui-repeater__card {padding: 16px;border: 1px solid #e2e8f0;border-radius: 8px;margin-bottom: 12px;}.gui-repeater__add-btn,.gui-repeater__remove-btn {padding: 8px 16px;border-radius: 6px;}Walk each widget reference page and copy its anatomy into your stylesheet as a starting point.
-
Style validation feedback
Section titled “Style validation feedback”Two classes handle the inline messages every input renders:
.gui-widget__hint {font-size: 14px;color: #64748b;margin-top: 4px;}.gui-widget__error {font-size: 14px;color: #ef4444;margin-top: 4px;}Combine
[aria-invalid='true']on the input with.gui-widget__errorfor a coherent invalid-state look. -
Style the action buttons
Section titled “Style the action buttons”Buttons render as
<button class="gui-button">plus modifier classes for variants and icon position:.gui-button {padding: 10px 20px;border-radius: 6px;border: none;background: #3b82f6;color: white;font-weight: 500;cursor: pointer;&:hover {background: #2563eb;}&:disabled {background: #cbd5e1;cursor: not-allowed;}}Any button you add via
gui.actions.button(whetheractionType: 'submit'or a customonClick) renders with the same.gui-buttonclass — one rule covers all of them. -
Sanity-check responsive and RTL behavior
Section titled “Sanity-check responsive and RTL behavior”Container queries are baked into the widget HTML; you only need to write the CSS that responds to them. Use the
@containerquery syntax to react to the form’s container width:@container (max-width: 480px) {.gui-flex__widget {gap: 8px;}.gui-widget input {font-size: 14px;}}For RTL, prefer logical properties (
inset-inline-end,padding-inline-start,margin-inline-end) rather thanleft/right. The engine flips text direction automatically when the active locale is RTL — your CSS just needs to flow with it.
Reuse the design tokens (optional)
Section titled “Reuse the design tokens (optional)”Even in headless mode you can opt back into GolemUI’s design tokens — just define the same --gui-* CSS custom property names on :root:
:root { --gui-color-primary-500: #3b82f6; --gui-radius-md: 8px; --gui-space-3: 12px; /* …whichever tokens you want to reuse… */}Now your hand-written rules can read var(--gui-color-primary-500) instead of hard-coded values, and you keep the door open for mixing in parts of the courtesy layer later if you change your mind. See Customization / Design tokens reference for the full token list.
Widget anatomy
Section titled “Widget anatomy”Every widget reference page (e.g. Textinput, Dropdown) ends with an Anatomy section that shows the rendered HTML. Use these as the contract you style against.
See also
Section titled “See also”- Styling Overview — headless vs courtesy layer, dark mode, responsive scaling, RTL.
- Customization — CSS variables, class-based overrides, design tokens, icon customization.
- Theming — built-in themes (Clay), creating named themes, scoped themes.
- Widgets Reference — per-widget HTML anatomy.