Pure-CSS styled replacements for native form controls. Each keeps the real <input> in the DOM (accessible, form-submittable) and renders a styled sibling that reacts to :checked. No JavaScript.
Every form component uses the same pattern: the native <input> is visually hidden (but stays in the DOM for keyboard + screen-reader support), and a styled sibling element is rendered next to it.
The visual element reads its size and color from the wrapping <label> (currentColor + em units), so you scale the whole control by applying Dopamine classes to the label.
Markup contract: the <input> must come before the visual sibling in the DOM. The CSS uses :checked ~ .foo__box to flip the visual on state change.
All four form controls in one file (CSS):
<link rel="stylesheet" href="css/components/forms/forms.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/forms';
Each control is also available as its own individual file — use those if you only need one or two (see the per-control sections below).
Minimal base for text-like <input>s (text, email, password, search, tel, url, number, date, time) and <textarea>. Resets browser defaults and keeps everything at currentColor / inherit — size, radius, and colors come from Dopamine classes applied on top.
<div>
<label for="email">Email</label>
<input type="email" id="email" class="df-input" placeholder="jane@example.com">
</div>
<div>
<label for="message">Message</label>
<textarea id="message" class="df-input" rows="4"></textarea>
</div>
Pair the <label for> with the input's id so clicking the label text focuses the input. Apply Dopamine classes directly on the .df-input for padding, radius, and font-size.
<link rel="stylesheet" href="css/components/forms/input.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/input';
df-input — applies to any text-like <input> or <textarea>. Border uses currentColor; font + color are inherited.
textarea.df-input — automatically gets resize: vertical and a sensible minimum height.
Styled checkbox using the visually-hidden-input + sibling-selector technique. Native <input> stays in the DOM for accessibility and form submission; a styled .df-checkbox__box sibling visualizes the state via :checked ~ .checkbox__box. Pure CSS — no JS.
<label class="df-checkbox">
<input type="checkbox" class="df-checkbox__input">
<span class="df-checkbox__box"></span>
<span class="df-checkbox__label">Label text</span>
</label>
The <input> must come before .df-checkbox__box in the DOM (the sibling selector depends on it). The box and label inherit currentColor and font-size — scale with Dopamine classes on .df-checkbox.
<link rel="stylesheet" href="css/components/forms/checkbox.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/checkbox';
df-checkbox — wrapping <label> (click anywhere inside it toggles the input).
df-checkbox__input — the real <input type="checkbox">, visually hidden but keyboard-focusable.
df-checkbox__box — the styled square; inherits currentColor for border + fill.
df-checkbox__label — the text (optional; omit for a box-only checkbox).
Same technique as checkbox but circular. Radios with the same name="" attribute belong to one exclusive group. Pure CSS.
<label class="df-radio">
<input type="radio" name="plan" class="df-radio__input">
<span class="df-radio__box"></span>
<span class="df-radio__label">Option A</span>
</label>
<label class="df-radio">
<input type="radio" name="plan" class="df-radio__input">
<span class="df-radio__box"></span>
<span class="df-radio__label">Option B</span>
</label>
<link rel="stylesheet" href="css/components/forms/radio.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/radio';
df-radio — wrapping <label>.
df-radio__input — the real <input type="radio">.
df-radio__box — the styled circle.
df-radio__label — the text.
Styled toggle switch (pill track + sliding thumb). Uses <input type="checkbox"> underneath — same form semantics as a checkbox, different visual. Pure CSS.
<label class="df-switch">
<input type="checkbox" class="df-switch__input">
<span class="df-switch__track"></span>
<span class="df-switch__label">Label text</span>
</label>
<link rel="stylesheet" href="css/components/forms/switch.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/switch';
df-switch — wrapping <label>.
df-switch__input — the real <input type="checkbox">.
df-switch__track — the pill-shaped track; thumb is a ::after inside it.
df-switch__label — the text.
Native <select> with a styled rounded chevron. The dropdown option list stays native — the browser/OS handles it, which keeps accessibility, keyboard navigation, and mobile pickers intact. Only the closed control (border, padding, arrow) is styled.
<label for="plan">Plan</label>
<select id="plan" class="df-select">
<option>Free</option>
<option>Pro</option>
</select>
Apply Dopamine classes directly on the .df-select for padding, radius, and font-size — same pattern as .df-input.
<link rel="stylesheet" href="css/components/forms/select.css">
Or via Sass:
@use 'dopamine-fluid/addons/components/forms/select';
df-select — applies to a native <select>. Border uses currentColor; font + color are inherited.
The chevron stroke is a fixed neutral — override background-image on .df-select to retheme.