Skip to content

Theming

ts
import 'declarative-forms/styles.css';

Two stylesheets

The tokens below are the ones the default stylesheet declares, with its values. The classic stylesheet reads the same core tokens with the v1 values and a smaller set; both are listed here.

The stylesheet is built on a --dl-* custom-property layer. Retheme by setting tokens rather than overriding rules.

Setting tokens

Tokens are declared at zero specificity via :where(:root), so anything you write wins — no !important needed:

css
:root {
  --dl-accent: #6d4aff;
  --dl-radius: 8px;
  --dl-font-family: 'Inter', system-ui, sans-serif;
}

Scope them to retheme one dialog:

css
.myOverlay {
  --dl-accent: #c0392b;
}
ts
form.openInModal({ wrapperClassNames: ['myOverlay'] });

If you add colours of your own, redeclare them under :root[data-dl-theme='dark'] as well — see Themes.

The tokens

Typography

TokenDefault
--dl-font-familysystem UI stack
--dl-font-size0.9375rem
--dl-font-size-sm0.8125rem
--dl-line-height1.5

Neutrals

TokenLightUsed for
--dl-surface#ffffffDialog background
--dl-surface-muted#f6f7f9Lower bar, cards, list rows
--dl-surface-sunken#eef0f4Multi-select pills
--dl-surface-raised#ffffffInputs and popups, distinct from the dialog
--dl-text-color#1b1f27Body text
--dl-label-color#39414fField labels
--dl-text-muted#6b7484Secondary text, placeholders, help
--dl-line-color#e0e4eaInput borders
--dl-line-strong#cbd2dcBorders that need more presence

Accent and danger

TokenLightUsed for
--dl-accent#12968fFilled buttons, active tab, checked box
--dl-accent-hover#0e807aHover state of the above
--dl-accent-contrast#ffffffText on a filled accent button
--dl-accent-softrgba(18,150,143,0.1)Tinted hover and selected states
--dl-accent-ringrgba(18,150,143,0.28)Focus-ring colour
--dl-danger#d1394bRemove buttons
--dl-danger-softrgba(209,57,75,0.1)Their hover tint

Every colour above is redefined for dark mode. See Themes.

Shape and elevation

TokenDefault
--dl-overlayrgba(17,22,29,0.42)
--dl-overlay-blur4px
--dl-radius-sm / --dl-radius6px / 9px
--dl-radius-lg / --dl-radius-pill14px / 999px
--dl-shadow-smCards and inputs
--dl-shadow-modalThe dialog
--dl-shadow-popupThe combobox popup

Metrics

TokenDefault
--dl-gap1.125rem (vertical rhythm)
--dl-field-width100% (fluid)
--dl-modal-width30rem
--dl-large-modal-width52rem
--dl-modal-padding1.5rem
--dl-control-padding-y / -x0.55rem / 0.75rem
--dl-stack-peek / --dl-stack-inset8px / 12px — the card edges behind a stacked dialog
--dl-transition140ms cubic-bezier(0.4,0,0.2,1)

Tooltips

TokenLight
--dl-tooltip-background#eef0f4
--dl-tooltip-color#5b6473
--dl-tooltip-bubble-background#22272f
--dl-tooltip-bubble-color#eceef2
--dl-tooltip-success-background / -color#dcf5e7 / #11794b
--dl-tooltip-warning-background / -color#fdf0cd / #7a5407
--dl-tooltip-error-background / -color#fbe0e3 / #96222f

Combobox

Read by both the stylesheet and the CSS <dl-select> injects itself:

TokenDefault
--dl-options-backgroundfollows --dl-surface-raised
--dl-options-text-colorfollows --dl-text-color
--dl-options-inactive-text-colorfollows --dl-text-muted
--dl-options-option-hover-colorfollows --dl-accent-soft
--dl-options-padding-top / -bottom0.5rem
--dl-options-padding-right / -left0.75rem
--dl-focused-line-colorfollows --dl-accent
--dl-drop-down-icon-margin-top7px
--dl-select-loading-col1 / -col2#eef0f4 / #e2e6ec

Also set the combobox tokens on dl-option

<dl-select> moves its options into a popup attached to document.body while open, so they leave the subtree your tokens may be scoped to. If you scope tokens to anything narrower than :root, declare the --dl-options-* ones on dl-option as well. See the DOM contract.

The classic stylesheet

ts
import 'declarative-forms/classic.css';

The classic look is tokenised too — it is the v1 stylesheet with every hardcoded value lifted into a --dl-* property, each var() carrying the v1 value as its fallback. It declares a smaller set, with these values:

TokenClassic defaultUsed for
--dl-font-family'Rubik', sans-serif
--dl-font-size0.9em
--dl-accent#1ea6a3Buttons, active tab, checked box, focused card
--dl-accent-hover#22bb8bButton hover border
--dl-danger#fe070bRemove buttons
--dl-surface#fffDialog background
--dl-surface-muted#f8f8f8Lower bar, cards, list rows
--dl-surface-sunken#eeeMulti-select tags
--dl-overlayrgba(0,0,0,0.6)Modal backdrop
--dl-text-color#545454Body text
--dl-label-color#58565cField labels
--dl-line-color#dddInput borders
--dl-focused-line-color#bbbFocused input borders
--dl-border-muted#e7e6e6List row borders
--dl-radius4px
--dl-field-width400px
--dl-modal-width416px
--dl-large-modal-width800px

Its tooltip and combobox tokens carry the same names as above with the v1 values; the -soft, -ring, -raised, -muted, -strong, shadow, stack and transition tokens are specific to the default stylesheet and have no effect here.

Dark mode in the classic stylesheet

There is no automatic switch. The stylesheet ships a body.dark-theme block that redefines the palette:

html
<body class="dark-theme"></body>

To drive it from the OS preference instead, re-declare the same tokens:

css
@media (prefers-color-scheme: dark) {
  :root {
    --dl-surface: #282727;
    --dl-surface-muted: #322f2f;
    --dl-text-color: #d1cfd8;
    --dl-line-color: #4a4747;
    --dl-focused-line-color: #6d6a6a;
    --dl-border-muted: #e7e6e6;
    --dl-select-loading-col1: #282727;
    --dl-select-loading-col2: #505656;
  }
}

The default stylesheet does this for you — that is data-dl-theme.

Where the combobox CSS lives

The form and modal chrome ship in the stylesheet you import. The structural styles for <dl-select> — absolute positioning of the popup, show/hide — are injected from JavaScript the first time a combobox connects, because the component does not function without them. They read the same tokens, so theming is uniform.

That injection is lazy and happens once; importing the library on its own adds nothing to the page.

Going beyond tokens

Every class name is a documented, frozen part of the API, so targeted overrides are safe:

css
.dl-modal .modal {
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.dl-form label {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

Per-field hooks are available too: every wrapper carries #dl-form-field-wrapper-for-<name>, and className on a descriptor adds your own classes.

Released under the MIT License.