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/fscss@1.1.24/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>
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);
}
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 */
}
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>
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/fscss@1.1.24/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>
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/fscss@1.1.24/exec.min.js" defer></script>
<style>
@import((*) from form)
</style>
Full docs, token reference, and markup gotchas (input-before-label ordering, the placeholder=" " requirement, sibling-selector rules):
Top comments (0)