Live Validation Summary
A copy-paste form states component in pure HTML & CSS. Zero dependencies, framework-agnostic, MIT-licensed.
Form StatesHTMLCSSany framework
Copy into your project
HTML
<!-- The banner needs zero JS: :has() reveals it whenever any input
inside the form carries aria-invalid="true". -->
<form class="nuda-fs2-has-summary" novalidate>
<div class="nuda-fs2-has-summary__row">
<label for="fs2-hs-email">Email</label>
<input id="fs2-hs-email" type="email" value="not-an-email" required
aria-invalid="true" aria-describedby="fs2-hs-email-err">
<span id="fs2-hs-email-err" class="nuda-fs2-has-summary__err">Enter a valid email address.</span>
</div>
<div class="nuda-fs2-has-summary__row">
<label for="fs2-hs-pass">Password</label>
<input id="fs2-hs-pass" type="password" value="secret123" required minlength="8">
</div>
<div class="nuda-fs2-has-summary__banner" role="status" aria-live="polite">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M12 8v5M12 16h.01" />
</svg>
<span>1 field needs your attention.</span>
</div>
</form>CSS
.nuda-fs2-has-summary {
width: 100%;
max-width: 280px;
display: flex;
flex-direction: column;
gap: 12px;
font-family: ui-sans-serif,system-ui,sans-serif;
}
.nuda-fs2-has-summary__row {
display: flex;
flex-direction: column;
gap: 5px;
}
.nuda-fs2-has-summary__row label {
font-size: 12px;
font-weight: 600;
color: #cfcfcf;
}
.nuda-fs2-has-summary__row input {
height: 44px;
padding: 0 12px;
background: #161616;
border: 1px solid rgba(255,255,255,.12);
border-radius: 9px;
color: #fafafa;
font-size: 13px;
box-sizing: border-box;
transition: border-color .2s;
}
.nuda-fs2-has-summary__row input:focus-visible {
outline: 2px solid #e4ff54;
outline-offset: 2px;
}
.nuda-fs2-has-summary__row input[aria-invalid="true"] {
border-color: #ff8080;
}
.nuda-fs2-has-summary__err {
font-size: 11px;
color: #ff9d9d;
}
.nuda-fs2-has-summary__banner {
display: none;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 9px;
background: rgba(255,128,128,.1);
border: 1px solid rgba(255,128,128,.3);
color: #ff9d9d;
font-size: 12px;
}
.nuda-fs2-has-summary__banner svg {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.nuda-fs2-has-summary:has(input[aria-invalid="true"]) .nuda-fs2-has-summary__banner {
display: flex;
}
@media (prefers-reduced-motion:reduce) {
.nuda-fs2-has-summary__row input {
transition: none !important;
}
}
How to use Live Validation Summary
Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.
Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.