Skip to content

FormDSL CSS Theming Reference

For LLMs generating styled FormDSL forms. This document covers the complete CSS theming system for @formdsl/solid and @formdsl/react. Both frameworks share the same stylesheet.

Quick Start

tsx
// 1. Import the stylesheet (once, in your app entry)
import '@formdsl/solid/styles'
// or for React:
import '@formdsl/react/styles'

// 2. Override variables in your own CSS
.form {
  --fb-primary: #e11d48;
  --fb-radius: 0;
}

That's it. Every --fb-* variable is overridable, and user styles automatically win over FormDSL defaults thanks to the CSS layer system.


CSS Layer System

FormDSL uses three ordered CSS layers:

@layer normalize, formbase.reset, formbase.base, formbase.theme;
LayerPurpose
formbase.resetNormalize form elements (box-sizing, font inheritance)
formbase.baseStructural styles (layout, spacing, grid, flex)
formbase.themeVisual styles (colors, borders, shadows, radii)

Key rule: Any CSS you write outside a @layer block automatically overrides all three layers. You never need !important.

css
/* This wins over everything in formbase.* layers */
.form .field__input {
  border: 2px solid navy;
}

Theme Variables

All variables are defined on .form and .form-layout inside @layer formbase.theme. Override them on .form, a wrapper class, or :root.

Colors

VariableLight DefaultDescription
--fb-primaryvar(--blue-6)Primary brand color (buttons, links, accents)
--fb-primary-hovervar(--blue-7)Primary hover state
--fb-primary-activevar(--blue-8)Primary active/pressed state
--fb-errorvar(--red-6)Error state color
--fb-error-lightvar(--red-1)Error background tint
--fb-warningvar(--yellow-6)Warning color
--fb-warning-lightvar(--yellow-1)Warning background tint
--fb-successvar(--teal-6)Success color
--fb-success-lightvar(--teal-1)Success background tint
--fb-infovar(--blue-6)Info color
--fb-info-lightvar(--blue-1)Info background tint

Text

VariableLight DefaultDescription
--fb-textvar(--gray-8)Body text
--fb-text-mutedvar(--gray-6)Descriptions, placeholders, secondary text
--fb-text-headingvar(--gray-9)Section titles, headings

Surfaces & Borders

VariableLight DefaultDescription
--fb-surfacevar(--gray-0)Input backgrounds, cards, panels
--fb-surface-altvar(--gray-1)Alternating/highlighted surfaces
--fb-bordervar(--gray-4)Input borders, dividers
--fb-border-lightvar(--gray-3)Subtle borders (section outlines, repeat items)

Spacing

All spacing maps to Open Props size tokens.

VariableOpen PropsApprox. Value
--fb-space-xsvar(--size-1)0.25rem
--fb-space-smvar(--size-2)0.5rem
--fb-space-mdvar(--size-3)0.75rem
--fb-space-lgvar(--size-5)1.25rem
--fb-space-xlvar(--size-7)1.75rem

Typography

VariableDefaultDescription
--fb-fontvar(--font-sans)Font family
--fb-font-sizevar(--font-size-1)Base font size (~1rem)
--fb-font-size-smvar(--font-size-0)Small text (~0.875rem)
--fb-font-size-lgvar(--font-size-2)Large text (~1.125rem)
--fb-font-weight-normalvar(--font-weight-4)Normal weight (400)
--fb-font-weight-mediumvar(--font-weight-5)Medium weight (500)
--fb-font-weight-boldvar(--font-weight-6)Bold weight (600)

Radius, Focus & Transitions

VariableDefaultDescription
--fb-radiusvar(--radius-2)Default border radius (~0.375rem)
--fb-radius-smvar(--radius-1)Small radius (~0.25rem)
--fb-focus-ring0 0 0 3px var(--blue-3)Focus ring box-shadow
--fb-focus-ring-error0 0 0 3px var(--red-3)Error focus ring
--fb-transition150ms var(--ease-2)Default transition timing

Dark Mode

FormDSL supports dark mode via two mechanisms:

1. System Preference (automatic)

Uses @media (prefers-color-scheme: dark) with an opt-out:

css
/* Active when system is dark AND html doesn't have data-theme="light" */
:root:not([data-theme="light"]) .form { /* dark overrides */ }

2. Manual Toggle

Set data-theme="dark" on <html>:

html
<html data-theme="dark">
css
:root[data-theme="dark"] .form { /* dark overrides */ }

Dark Mode Variable Values

VariableDark Value
--fb-primaryvar(--blue-4)
--fb-primary-hovervar(--blue-3)
--fb-primary-activevar(--blue-2)
--fb-errorvar(--red-4)
--fb-error-lightcolor-mix(in srgb, var(--red-5) 15%, var(--gray-8))
--fb-warningvar(--yellow-4)
--fb-warning-lightcolor-mix(in srgb, var(--yellow-5) 15%, var(--gray-8))
--fb-successvar(--teal-4)
--fb-success-lightcolor-mix(in srgb, var(--teal-5) 15%, var(--gray-8))
--fb-infovar(--blue-4)
--fb-info-lightcolor-mix(in srgb, var(--blue-5) 15%, var(--gray-8))
--fb-textvar(--gray-2)
--fb-text-mutedvar(--gray-4)
--fb-text-headingvar(--gray-1)
--fb-surfacevar(--gray-8)
--fb-surface-altvar(--gray-7)
--fb-borderrgba(255, 255, 255, 0.15)
--fb-border-lightrgba(255, 255, 255, 0.1)
--fb-focus-ring0 0 0 3px color-mix(in srgb, var(--blue-4) 40%, transparent)
--fb-focus-ring-error0 0 0 3px color-mix(in srgb, var(--red-4) 40%, transparent)

Built-in Themes

default

The standard theme. Clean, modern look with bordered sections, rounded inputs, and blue primary color.

tsx
<Form id="my-form" theme="default" ...>

printed

Paper-like aesthetic with strong contrast and minimal chrome. Activated via the theme prop:

tsx
<Form id="my-form" theme="printed" ...>

CSS class: .form--theme-printed

Key differences from default:

  • Inputs: underline-only (no full border), light indigo background
  • Sections: dark horizontal dividers, no border/background
  • Section titles: uppercase, letter-spaced, dotted underline
  • Radius: all 0 (sharp corners)
  • Primary color: indigo (var(--indigo-7))
  • Text: maximum contrast (no greys for muted text)
  • Buttons: sharp-cornered, solid borders
  • Sidebar: sharp right border, no rounding

Section Variants

Set via the variant prop on <Section>:

VariantClassEffect
default.form__sectionBordered card with alt-surface background
plain.form__section--plainNo border, no background, no padding
compact.form__section--compactReduced padding, smaller title
highlighted.form__section--highlightedLeft border accent + tinted primary background
tsx
<Section title="Notes" variant="highlighted">

Horizontal Field Layout

Set fieldLayout="horizontal" on a <Section> to place labels and inputs side by side (35% / 65% grid). Falls back to stacked on mobile.

tsx
<Section title="Details" fieldLayout="horizontal">

CSS class: .form__section--horizontal


Form Modes

Compact Mode

Reduced spacing throughout the form. Set via compact prop on <Form>:

tsx
<Form id="my-form" compact ...>

CSS class: .form--compact

Effects:

  • Row grid columns shrink to minmax(min(100%, 120px), 1fr)
  • Field wrappers constrain to max-content width

Form Width Presets

Set via width prop on <Form>:

tsx
<Form id="my-form" width="lg" ...>
ValueWidth
'xs'480px
'sm'640px
'md'800px (default)
'lg'1000px
'xl'1200px

Preview Mode

Read-only display. Inputs become static text, interactivity is disabled.

CSS class: .form--preview

Effects:

  • Inputs lose borders and background
  • Answered fields highlighted with var(--yellow-1) background
  • Pointer events disabled on interactive elements

Key CSS Selectors

Form Root

SelectorElement
.formMain form container
.form-layoutFull-page layout wrapper (flex column, min-height 100vh)
.form-layout__headerSticky top header bar
.form-layout__bodyMain content area (flex row with sidebar)
.form-layout__contentForm content column
.form-layout__footerBottom footer bar

Sections

SelectorElement
.form__sectionSection container
.form__section-titleSection heading (has primary-colored bottom border)
.form__section-bodySection content wrapper
.form__section-iconOptional icon before section title

Fields & Inputs

SelectorElement
.fieldField wrapper (label + input + error)
.field__labelField label text
.field__requiredRequired asterisk (*)
.field__descriptionHelp text below label
.field__inputText/email/number/date inputs, textareas
.field__input--textareaTextarea variant
.field__input--selectSelect dropdown
.field__input--errorInput in error state
.field__errorError message text
.field__input-wrapperContainer for input + prefix/suffix adornments
.field__input-adornPrefix or suffix adornment
.field__checkbox-labelBoolean checkbox + label row
.field__radio-groupRadio option container
.field__radio-optionSingle radio option row
.field__checkbox-groupMulti-checkbox container
.field__checkbox-optionSingle checkbox option row
.field__tag-pillTag/multi-select pill

Buttons & Actions

SelectorElement
.form__actionsBottom action bar (submit/save buttons)
.form__submitPrimary submit button
.form-header__submitHeader submit button (sidebar/stepper layout)
.form-header__ctaHeader secondary CTA button
.form-nav-actionsStepper/tabs bottom navigation bar
.form-nav-actions__backBack button
.form-nav-actions__nextNext/continue button
.form-nav-actions__saveSave button (in nav bar)
.form-nav-actions__submitSubmit button (in nav bar, last section)

Banners & Cards

SelectorElement
.bannerAlert banner container
.banner--infoInfo banner (blue)
.banner--warningWarning banner (yellow)
.banner--errorError banner (red)
.banner--successSuccess banner (green)
.cardCard container (shadow, padding)
.info-boxInline info/warning box
.info-box--infoInfo variant
.info-box--warningWarning variant

Repeat Groups

SelectorElement
.form__repeatRepeat group container
.form__repeat-itemSingle repeat item card
.form__repeat-item-headerItem header (label + actions)
.form__repeat-item-removeRemove item button
.form__repeat-add"Add item" button (dashed border)
.form__repeat-undoUndo-remove notification bar
SelectorElement
.form-sidebarSidebar navigation panel
.form-sidebar__itemSidebar section link
.form-sidebar__item--activeActive section (blue tint)
.form-sidebar__item--completeCompleted section (green indicator)
.form-sidebar__progress-ringCircular progress indicator
.form-stepperStepper navigation bar
.form-stepper__stepSingle step item
.form-stepper__step--currentCurrent step (blue circle)
.form-stepper__step--completedCompleted step (green circle)
.form-stepper__numberStep number circle
.form-stepper__titleStep label text
.form-tabsTab navigation container
.form-tabs__tabSingle tab button
.form-tabs__tab--activeActive tab (gray background, border)
.mobile-navMobile dropdown navigation (visible at <= 640px)
.mobile-nav__triggerCurrent section dropdown trigger
.mobile-nav__dropdownDropdown menu overlay

Layout Helpers

SelectorElement
.form__rowGrid row (auto-fit columns)
.form__row--gap-smRow with small gap
.form__row--gap-lgRow with large gap
.form__stackVertical flex container
.form__dividerHorizontal rule
.form-progressProgress bar container
.form-progress__barProgress bar fill

Override Recipes

Brand Color Swap

css
.form {
  --fb-primary: #e11d48;
  --fb-primary-hover: #be123c;
  --fb-primary-active: #9f1239;
  --fb-focus-ring: 0 0 0 3px rgba(225, 29, 72, 0.3);
}

Custom Font Family

css
.form,
.form-layout {
  --fb-font: 'Inter', system-ui, sans-serif;
}

Rounded vs Sharp Corners

css
/* Fully rounded */
.form {
  --fb-radius: 9999px;
  --fb-radius-sm: 9999px;
}

/* Sharp (no rounding) */
.form {
  --fb-radius: 0;
  --fb-radius-sm: 0;
}

Dense Spacing

css
.form {
  --fb-space-xs: 0.125rem;
  --fb-space-sm: 0.25rem;
  --fb-space-md: 0.5rem;
  --fb-space-lg: 0.75rem;
  --fb-space-xl: 1rem;
}

Scoped Theme (Class on a Wrapper)

css
.my-embedded-form .form {
  --fb-primary: #7c3aed;
  --fb-surface: #faf5ff;
  --fb-surface-alt: #f3e8ff;
  --fb-border: #c4b5fd;
  --fb-border-light: #ddd6fe;
}
tsx
<div class="my-embedded-form">
  <Form id="my-form" ...>
    ...
  </Form>
</div>

Dark-Mode-Only Overrides

css
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .form {
    --fb-primary: #a78bfa;
    --fb-surface: #1e1b4b;
  }
}

:root[data-theme="dark"] .form {
  --fb-primary: #a78bfa;
  --fb-surface: #1e1b4b;
}

Custom Section Title Style

css
.form .form__section-title {
  border-bottom: none;
  background: var(--fb-primary);
  color: white;
  padding: var(--fb-space-sm) var(--fb-space-md);
  border-radius: var(--fb-radius) var(--fb-radius) 0 0;
  margin: 0 0 var(--fb-space-md);
}

Muted Submit Button

css
.form .form__submit {
  background: var(--fb-text);
  border-radius: 0;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}

.form .form__submit:hover {
  background: var(--fb-text-heading);
}

Responsive Behavior

FormDSL uses a single breakpoint at 640px.

What changes at <= 640px:

ComponentBehavior
.formPadding reduces to --fb-space-sm
.form__rowCollapses to single column (grid-template-columns: 1fr)
.form-sidebarHidden; replaced by .mobile-nav dropdown
.form-layout__headerStacks vertically, buttons go full-width
.form-stepperCircle + connector sizes shrink
.form-tabsTabs get smaller padding, scroll horizontally
.form__section--horizontalFalls back to stacked (label above input)

Forcing Single Column on Desktop

css
.form .form__row {
  grid-template-columns: 1fr;
}

Tips & Anti-patterns

Do:

  • Override --fb-* variables instead of writing raw property overrides
  • Put custom CSS outside any @layer to automatically win specificity
  • Override both --fb-primary-hover and --fb-primary-active when changing --fb-primary
  • Set variables on both .form and .form-layout when customizing layout-level colors
  • Duplicate dark-mode overrides in both the @media block and [data-theme="dark"] rule for full coverage

Don't:

  • Use !important — the layer system makes it unnecessary
  • Override variables on :root if you have multiple forms with different themes on one page (scope to a wrapper class instead)
  • Forget that spacing variables affect the entire form — changing --fb-space-md affects fields, rows, sections, and banners
  • Override structural .base layer properties (grid, flex, display) with variable changes — variables only control the .theme layer values
  • Target internal class names that aren't listed above — they may change between versions

Stylesheet Entry Point

The SDK bundles all styles through a single import:

ts
// SolidJS
import '@formdsl/solid/styles'
// React
import '@formdsl/react/styles'

This loads (in order):

  1. form.css — variables, reset, base layout, theme
  2. sidebar.css — sidebar navigation + header layout
  3. stepper.css — stepper navigation + nav actions
  4. tabs.css — tab navigation
  5. review.css — submission review mode
  6. mobile.css — mobile navigation dropdown
  7. themes/printed.css — printed theme overrides

All styles are scoped to FormDSL class names (.form, .field__*, .form-sidebar, etc.) and will not leak into your application.