Floating labels. Inline validation. Custom checkboxes, radios, and a toggle switch. A gradient button with a press-down micro-interaction. Every bit of it below is CSS — no form library, no useState, no event listener wiring up a class toggle.
That’s form.fscss — the module in the FSCSS ecosystem. Same philosophy each time: solve the hard visual problem once, ship it as importable mixins, let the browser do the actual work.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/exec.min.js" defer></script>
<style>
@import((*) from form)
@form-root()
@form-group(.form-group)
@form-input(.form-input)
@form-label(.form-label)
@form-float(.form-group, .form-input, .form-label)
@form-checkbox(.form-checkbox)
@form-btn(.form-btn)
@form-btn-primary(.form-btn-primary)
</style>
<div class="form-group">
<input class="form-input" type="text" placeholder=" ">
<label class="form-label">Full name</label>
</div>
<label class="form-checkbox">
<input type="checkbox" checked><span></span> I agree to the Terms
</label>
<button class="form-btn form-btn-primary">Create account</button>
Enter fullscreen mode Exit fullscreen mode
The two tricks doing all the work
Forms feel like they need JavaScript because most tutorials reach for it immediately. Two native CSS mechanisms cover almost everything a “modern” form needs.
Floating labels run entirely on :placeholder-shown. Give the input placeholder=" " — a literal space, not empty — and the browser now knows, purely in CSS, whether the field is empty and unfocused:
.form-input:focus + .form-label,
.form-input:not(:placeholder-shown) + .form-label {
top: -9px;
font-size: 11px;
color: var(--form-accent);
}
Enter fullscreen mode Exit fullscreen mode
No state, no class toggling on keyup. The label just reacts to what the browser already knows about the input.
Checkboxes, radios, and the switch all use the classic checkbox-hack: the real <input> stays in the DOM (so it keeps native keyboard support and form submission) but is visually hidden, and a sibling <span> becomes the thing you actually see:
.form-checkbox input { opacity: 0; position: absolute; }
.form-checkbox input:checked + span::after {
transform: rotate(45deg) scale(1); /* the checkmark, drawn from two borders */
}
Enter fullscreen mode Exit fullscreen mode
The checkmark itself isn’t an icon font or an SVG — it’s two border sides on a pseudo-element, rotated 45 degrees. Same trick powers the radio dot and the switch thumb slide, just with different shapes and transforms.
Validation
Add .is-error or .is-success straight to the field wrapper. One mixin call registers the rule; every field just reacts to its own class:
<div class="form-group is-error">
<input class="form-input" type="email" placeholder=" ">
<label class="form-label">Email address</label>
<span class="form-help">Please enter a valid email</span>
</div>
Enter fullscreen mode Exit fullscreen mode
Your actual validation logic — regex, an API check, whatever — still needs real JS somewhere. But the presentation layer of “this field is wrong, here’s why” needs nothing but toggling one class name. no state management for something.
Full example
<script src="https://cdn.jsdelivr.net/npm/[email protected]/exec.min.js" defer></script>
<style>
@import((*) from form)
@form-root()
body {
min-height: 100vh;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--form-bg);
font-family: 'Syne', sans-serif;
}
.card {
width: 100%;
max-width: 380px;
background: #1c1a2e;
border: 1px solid var(--form-border);
border-radius: 20px;
padding: 32px;
}
@form-group(.form-group)
@form-input(.form-input)
@form-label(.form-label)
@form-float(.form-group, .form-input, .form-label)
@form-help(.form-help)
@form-validation(.form-group)
@form-select(.form-select)
@form-select-arrow(.form-select-wrap)
@form-checkbox(.form-checkbox)
@form-switch(.form-switch)
@form-btn(.form-btn)
@form-btn-primary(.form-btn-primary)
</style>
<div class="card">
<div class="form-group">
<input class="form-input" type="text" placeholder=" ">
<label class="form-label">Full name</label>
</div>
<div class="form-group is-success">
<input class="form-input" type="text" placeholder=" " value="devtemple_creator">
<label class="form-label">Username</label>
<span class="form-help">Username is available</span>
</div>
<div class="form-select-wrap">
<select class="form-select">
<option>Creator</option>
<option>Buyer</option>
</select>
</div>
<label class="form-checkbox">
<input type="checkbox" checked><span></span> I agree to the Terms
</label>
<button class="form-btn form-btn-primary" style="width:100%;">Create account</button>
</div>
Enter fullscreen mode Exit fullscreen mode
Every visual state in that form — resting, focused, filled, valid, invalid, checked, hovered, pressed — is handled by CSS pseudo-classes doing what they were always built to do. JavaScript’s only job left is your logic: submit handling, async validation, whatever’s actually specific to your product.
Try it
<script src="https://cdn.jsdelivr.net/npm/[email protected]/exec.min.js" defer></script>
<style>
@import((*) from form)
</style>
Enter fullscreen mode Exit fullscreen mode
Full docs, token reference, and markup gotchas (input-before-label ordering, the placeholder=" " requirement, sibling-selector rules):