Skip to content

Live Validation Summary

A copy-paste form states component in pure HTML & CSS. Zero dependencies, framework-agnostic, MIT-licensed.

Form StatesHTMLCSSany framework
Enter a valid email address.
1 field needs your attention.

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.

More form states components

← Browse all NudaUI components