07 / 28
All 28 in 28 Tailwind CSS Input Fields
- 01Tailwind Floating Label Input
- 02Tailwind Underline Sweep Input
- 03Tailwind Neon Glow Input
- 04Tailwind Frosted Glass Input
- 05Tailwind Notched Outline Input
- 06Tailwind Drawn Border Input
- 07Tailwind Inline Validation Input
- 08Tailwind Search Input With Clear
- 09Tailwind Password Strength Meter
- 10Tailwind OTP Code Input
- 11Tailwind Rotating Placeholder Input
- 12Tailwind Tag Chip Input
- 13Tailwind Phone Input With Country
- 14Tailwind Stepper Number Input
- 15Tailwind File Drop Zone
- 16Tailwind Currency Input
- 17Tailwind Native Date Input
- 18Tailwind Time Range Input
- 19Tailwind Colour Picker Input
- 20Tailwind Voice Search Input
- 21Tailwind Email Suggestion Combobox
- 22Tailwind Auto-Grow Textarea
- 23Tailwind Inline Edit Field
- 24Tailwind Range Slider
- 25Tailwind URL Input With Protocol
- 26Tailwind Credit Card Input
- 27Tailwind Password Toggle Input
- 28Tailwind Brutalist Input
Tailwind Inline Validation Input
Live valid/invalid feedback with zero JavaScript, graded on :user-valid and :user-invalid rather than :valid/:invalid — so the field is never marked wrong before it has been touched. That is the gotcha the CSS original lists and does not fix.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tif-07 grid min-h-svh w-full place-items-center bg-tif07-bg px-6 py-14 font-tif07-sans" aria-label="Inline validation input demo">
<label class="group grid w-full max-w-70 gap-1.5 text-[11px] text-tif07-mut">
<span class="font-semibold tracking-[0.04em]">Email</span>
<span class="relative block after:absolute after:top-1/2 after:right-3 after:size-4.5 after:-translate-y-1/2 after:scale-50 after:rounded-full after:bg-center after:bg-no-repeat after:opacity-0 after:transition-[transform,opacity,background-color] after:duration-200 after:content-[''] group-has-[input:user-invalid]:after:scale-100 group-has-[input:user-invalid]:after:bg-tif07-bad group-has-[input:user-invalid]:after:opacity-100 group-has-[input:user-invalid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.6%22 stroke-linecap=%22round%22><path d=%22M2 2 L8 8 M8 2 L2 8%22/></svg>')] group-has-[input:user-valid]:after:scale-100 group-has-[input:user-valid]:after:bg-tif07-ok group-has-[input:user-valid]:after:opacity-100 group-has-[input:user-valid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.8%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><path d=%22M2 5 L4.2 7.2 L8 3%22/></svg>')] motion-reduce:after:transition-opacity">
<input type="email" name="tif07-email" autocomplete="email" placeholder="name@company.com" required
class="w-full rounded-lg border border-white/10 bg-tif07-field py-2.5 pr-9.5 pl-3.5 text-sm text-tif07-ink outline-none transition-colors duration-200 focus:border-tif07-acc user-invalid:border-tif07-bad/55 user-valid:border-tif07-ok/55">
</span>
<span class="text-[10.5px] text-tif07-mut">We'll never share your address.</span>
</label>
</div><div class="tif-07 grid min-h-svh w-full place-items-center bg-tif07-bg px-6 py-14 font-tif07-sans" aria-label="Inline validation input demo">
<label class="group grid w-full max-w-70 gap-1.5 text-[11px] text-tif07-mut">
<span class="font-semibold tracking-[0.04em]">Email</span>
<span class="relative block after:absolute after:top-1/2 after:right-3 after:size-4.5 after:-translate-y-1/2 after:scale-50 after:rounded-full after:bg-center after:bg-no-repeat after:opacity-0 after:transition-[transform,opacity,background-color] after:duration-200 after:content-[''] group-has-[input:user-invalid]:after:scale-100 group-has-[input:user-invalid]:after:bg-tif07-bad group-has-[input:user-invalid]:after:opacity-100 group-has-[input:user-invalid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.6%22 stroke-linecap=%22round%22><path d=%22M2 2 L8 8 M8 2 L2 8%22/></svg>')] group-has-[input:user-valid]:after:scale-100 group-has-[input:user-valid]:after:bg-tif07-ok group-has-[input:user-valid]:after:opacity-100 group-has-[input:user-valid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.8%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><path d=%22M2 5 L4.2 7.2 L8 3%22/></svg>')] motion-reduce:after:transition-opacity">
<input type="email" name="tif07-email" autocomplete="email" placeholder="name@company.com" required
class="w-full rounded-lg border border-white/10 bg-tif07-field py-2.5 pr-9.5 pl-3.5 text-sm text-tif07-ink outline-none transition-colors duration-200 focus:border-tif07-acc user-invalid:border-tif07-bad/55 user-valid:border-tif07-ok/55">
</span>
<span class="text-[10.5px] text-tif07-mut">We'll never share your address.</span>
</label>
</div>@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Improved, not just ported.
:valid/:invalidgated behind:not(:placeholder-shown)becomes:user-valid/:user-invalid— the browser now decides what "touched" means, and the placeholder is free to be a real hint. - Four
:has()rule blocks collapse intogroup-has-[…]:after:*utilities on the indicator itself. - The input carries its own
user-valid:/user-invalid:border colours, so the field and its chip cannot disagree.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Inline Validation Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-inline-validation-input/
Live valid/invalid feedback with zero JavaScript, graded on :user-valid and :user-invalid rather than :valid/:invalid — so the field is never marked wrong before it has been touched. That is the gotcha the CSS original lists and does not fix.
## HTML
```html
<div class="tif-07 grid min-h-svh w-full place-items-center bg-tif07-bg px-6 py-14 font-tif07-sans" aria-label="Inline validation input demo">
<label class="group grid w-full max-w-70 gap-1.5 text-[11px] text-tif07-mut">
<span class="font-semibold tracking-[0.04em]">Email</span>
<span class="relative block after:absolute after:top-1/2 after:right-3 after:size-4.5 after:-translate-y-1/2 after:scale-50 after:rounded-full after:bg-center after:bg-no-repeat after:opacity-0 after:transition-[transform,opacity,background-color] after:duration-200 after:content-[''] group-has-[input:user-invalid]:after:scale-100 group-has-[input:user-invalid]:after:bg-tif07-bad group-has-[input:user-invalid]:after:opacity-100 group-has-[input:user-invalid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.6%22 stroke-linecap=%22round%22><path d=%22M2 2 L8 8 M8 2 L2 8%22/></svg>')] group-has-[input:user-valid]:after:scale-100 group-has-[input:user-valid]:after:bg-tif07-ok group-has-[input:user-valid]:after:opacity-100 group-has-[input:user-valid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.8%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><path d=%22M2 5 L4.2 7.2 L8 3%22/></svg>')] motion-reduce:after:transition-opacity">
<input type="email" name="tif07-email" autocomplete="email" placeholder="name@company.com" required
class="w-full rounded-lg border border-white/10 bg-tif07-field py-2.5 pr-9.5 pl-3.5 text-sm text-tif07-ink outline-none transition-colors duration-200 focus:border-tif07-acc user-invalid:border-tif07-bad/55 user-valid:border-tif07-ok/55">
</span>
<span class="text-[10.5px] text-tif07-mut">We'll never share your address.</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Inline Validation Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-inline-validation-input/
Live valid/invalid feedback with zero JavaScript, graded on :user-valid and :user-invalid rather than :valid/:invalid — so the field is never marked wrong before it has been touched. That is the gotcha the CSS original lists and does not fix.
## HTML
```html
<div class="tif-07 grid min-h-svh w-full place-items-center bg-tif07-bg px-6 py-14 font-tif07-sans" aria-label="Inline validation input demo">
<label class="group grid w-full max-w-70 gap-1.5 text-[11px] text-tif07-mut">
<span class="font-semibold tracking-[0.04em]">Email</span>
<span class="relative block after:absolute after:top-1/2 after:right-3 after:size-4.5 after:-translate-y-1/2 after:scale-50 after:rounded-full after:bg-center after:bg-no-repeat after:opacity-0 after:transition-[transform,opacity,background-color] after:duration-200 after:content-[''] group-has-[input:user-invalid]:after:scale-100 group-has-[input:user-invalid]:after:bg-tif07-bad group-has-[input:user-invalid]:after:opacity-100 group-has-[input:user-invalid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.6%22 stroke-linecap=%22round%22><path d=%22M2 2 L8 8 M8 2 L2 8%22/></svg>')] group-has-[input:user-valid]:after:scale-100 group-has-[input:user-valid]:after:bg-tif07-ok group-has-[input:user-valid]:after:opacity-100 group-has-[input:user-valid]:after:bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 10 10%22 fill=%22none%22 stroke=%22white%22 stroke-width=%221.8%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><path d=%22M2 5 L4.2 7.2 L8 3%22/></svg>')] motion-reduce:after:transition-opacity">
<input type="email" name="tif07-email" autocomplete="email" placeholder="name@company.com" required
class="w-full rounded-lg border border-white/10 bg-tif07-field py-2.5 pr-9.5 pl-3.5 text-sm text-tif07-ink outline-none transition-colors duration-200 focus:border-tif07-acc user-invalid:border-tif07-bad/55 user-valid:border-tif07-ok/55">
</span>
<span class="text-[10.5px] text-tif07-mut">We'll never share your address.</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tif07-bg: #12121a;
--color-tif07-bg: oklch(0.19 0.015 285);
--color-tif07-field: #1a1a22;
--color-tif07-field: oklch(0.23 0.015 285);
--color-tif07-ink: #f0eeff;
--color-tif07-ink: oklch(0.95 0.02 290);
--color-tif07-mut: #b8b6d4;
--color-tif07-mut: oklch(0.77 0.03 290);
--color-tif07-acc: #7c6cff;
--color-tif07-acc: oklch(0.62 0.2 288);
--color-tif07-ok: #22c55e;
--color-tif07-ok: oklch(0.73 0.19 145);
--color-tif07-bad: #ef4444;
--color-tif07-bad: oklch(0.63 0.22 27);
--font-tif07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tif-07 {
width: 100%;
}
.tif-07 ::selection {
background: var(--color-tif07-acc);
color: #12121a;
}
```How this works
The state is read once, at the top. group sits on the label; every reaction is a variant on the element that reacts:
class="after:opacity-0 after:scale-50
group-has-[input:user-valid]:after:opacity-100
group-has-[input:user-valid]:after:scale-100"Why :user-invalid matters. :invalid matches an empty required field on first paint, which is why the CSS version has to gate everything behind :not(:placeholder-shown). :user-invalid only matches after the user has interacted and left, which is both simpler and the behaviour you actually wanted.
Make it yours
- Rule: swap
requiredfor apattern,minlengthortype="url"— all feed the same variants. - Icons: the two data-URI backgrounds on
after:. Any inline SVG works; keep it under ~200 bytes. - Colours:
--color-tif07-okand--color-tif07-baddrive both border and chip. - Timing:
after:duration-200on the shared transition.
Gotchas — read before shipping
:user-valid/:user-invalidneed Chrome 119+, Safari 16.5+, Firefox 88+. Below that the field is unindicated but fully functional.- Data-URI SVGs must use single quotes inside the
url()and have every#escaped as%23, or the whole declaration is dropped. - An icon is not an error message. Pair this with real text for anything a user could get wrong twice.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 119+ | 16.5+ | 128+ | 119+ |
:user-valid/:user-invalid set the floor here, above Tailwind's own; the fallback is a plain field with no indicator.