Pure TailwindMIT licensed

Tailwind Notched Outline Input

The Material notched outline — rebuilt on a real <fieldset> and <legend>, so the browser cuts the gap for you. The CSS original fakes it with three bordered spans and an opaque label background that must exactly match the page; this version works on any surface, gradient included.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<div class="tif-05 grid min-h-svh w-full place-items-center bg-tif05-bg px-6 py-14 font-tif05-sans" aria-label="Notched outline input demo">
  <label class="group relative block w-full max-w-70">
    <input type="text" name="tif05-name" autocomplete="name" placeholder=" " required
      class="relative z-1 w-full bg-transparent px-3.5 py-3.5 text-sm text-tif05-ink outline-none placeholder:text-transparent">
    <span class="pointer-events-none absolute top-3.5 left-3.5 text-[13px] text-tif05-mut transition-opacity duration-150 group-focus-within:opacity-0 group-has-[input:not(:placeholder-shown)]:opacity-0">Full name</span>
    <fieldset aria-hidden="true" class="pointer-events-none absolute inset-x-0 -top-2 bottom-0 rounded-lg border border-tif05-acc/35 px-2 transition-colors duration-200 group-focus-within:border-tif05-acc">
      <legend class="ml-1 max-w-0 overflow-hidden p-0 text-[10px] font-semibold tracking-[0.06em] whitespace-nowrap text-tif05-acc uppercase opacity-0 transition-[max-width,padding,opacity] duration-200 group-focus-within:max-w-40 group-focus-within:px-1 group-focus-within:opacity-100 group-has-[input:not(:placeholder-shown)]:max-w-40 group-has-[input:not(:placeholder-shown)]:px-1 group-has-[input:not(:placeholder-shown)]:opacity-100 motion-reduce:transition-none">Full name</legend>
    </fieldset>
  </label>
</div>
@import "tailwindcss";

@theme {
  --color-tif05-bg: #07070f;
  --color-tif05-bg: oklch(0.13 0.012 280);
  --color-tif05-ink: #f0eeff;
  --color-tif05-ink: oklch(0.95 0.02 290);
  --color-tif05-mut: #b8b6d4;
  --color-tif05-mut: oklch(0.77 0.03 290);
  --color-tif05-acc: #2eb88a;
  --color-tif05-acc: oklch(0.7 0.13 165);
  --font-tif05-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tif-05 {
  width: 100%;
}

.tif-05 ::selection {
  background: var(--color-tif05-acc);
  color: #07070f;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tif05-bg: #07070f;
  --color-tif05-bg: oklch(0.13 0.012 280);
  --color-tif05-ink: #f0eeff;
  --color-tif05-ink: oklch(0.95 0.02 290);
  --color-tif05-mut: #b8b6d4;
  --color-tif05-mut: oklch(0.77 0.03 290);
  --color-tif05-acc: #2eb88a;
  --color-tif05-acc: oklch(0.7 0.13 165);
  --font-tif05-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tif-05 {
  width: 100%;
}

.tif-05 ::selection {
  background: var(--color-tif05-acc);
  color: #07070f;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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. The three-span grid-template-columns: 12px auto 1fr frame and its opaque label swatch are gone; a native fieldset/legend cuts the notch, so the field now works on gradients and images where the original visibly failed.
  • Four descendant selector chains (input:focus ~ .frame .cut > span) become group-focus-within: and group-has-[…]: on the two elements that actually change.
  • One fewer thing to keep in sync: there is no longer a background colour that must exactly match the page surface.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Notched Outline Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-notched-outline-input/

The Material notched outline — rebuilt on a real <fieldset> and <legend>, so the browser cuts the gap for you. The CSS original fakes it with three bordered spans and an opaque label background that must exactly match the page; this version works on any surface, gradient included.
## HTML
```html
<div class="tif-05 grid min-h-svh w-full place-items-center bg-tif05-bg px-6 py-14 font-tif05-sans" aria-label="Notched outline input demo">
  <label class="group relative block w-full max-w-70">
    <input type="text" name="tif05-name" autocomplete="name" placeholder=" " required
      class="relative z-1 w-full bg-transparent px-3.5 py-3.5 text-sm text-tif05-ink outline-none placeholder:text-transparent">
    <span class="pointer-events-none absolute top-3.5 left-3.5 text-[13px] text-tif05-mut transition-opacity duration-150 group-focus-within:opacity-0 group-has-[input:not(:placeholder-shown)]:opacity-0">Full name</span>
    <fieldset aria-hidden="true" class="pointer-events-none absolute inset-x-0 -top-2 bottom-0 rounded-lg border border-tif05-acc/35 px-2 transition-colors duration-200 group-focus-within:border-tif05-acc">
      <legend class="ml-1 max-w-0 overflow-hidden p-0 text-[10px] font-semibold tracking-[0.06em] whitespace-nowrap text-tif05-acc uppercase opacity-0 transition-[max-width,padding,opacity] duration-200 group-focus-within:max-w-40 group-focus-within:px-1 group-focus-within:opacity-100 group-has-[input:not(:placeholder-shown)]:max-w-40 group-has-[input:not(:placeholder-shown)]:px-1 group-has-[input:not(:placeholder-shown)]:opacity-100 motion-reduce:transition-none">Full name</legend>
    </fieldset>
  </label>
</div>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tif05-bg: #07070f;
  --color-tif05-bg: oklch(0.13 0.012 280);
  --color-tif05-ink: #f0eeff;
  --color-tif05-ink: oklch(0.95 0.02 290);
  --color-tif05-mut: #b8b6d4;
  --color-tif05-mut: oklch(0.77 0.03 290);
  --color-tif05-acc: #2eb88a;
  --color-tif05-acc: oklch(0.7 0.13 165);
  --font-tif05-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tif-05 {
  width: 100%;
}

.tif-05 ::selection {
  background: var(--color-tif05-acc);
  color: #07070f;
}
```

How this works

The gap is the browser's job. A legend inside a fieldset interrupts the border natively — no opaque swatch painted over a line, so the field can sit on a gradient:

<fieldset class="absolute -top-2 inset-x-0 bottom-0 rounded-lg border">
  <legend class="max-w-0 px-0 opacity-0
    group-focus-within:max-w-40 group-focus-within:px-1">…

Group, not peer. The legend is a descendant of a sibling, not a sibling, so peer-* cannot reach it. group on the label plus group-has-[…] reads the input's state from above and applies it below — the variant pair that replaces every descendant selector in this collection.

Make it yours

  • Accent: --color-tif05-acc drives resting border, focus border and legend in one edit.
  • Notch width cap: group-focus-within:max-w-40 — raise it for longer labels, it only needs to exceed the text.
  • Corner: rounded-lg on the fieldset, matched by nothing else. The fake-notch version needed two radii kept in sync.
  • Speed: duration-200 on the legend. Shorter reads as more responsive on dense forms.

Gotchas — read before shipping

  • The label text appears twice — visible placeholder-label and legend. Change one, change both, or the states disagree.
  • legend needs p-0 and an explicit ml-* reset: the UA stylesheet adds padding you did not ask for.
  • placeholder=&quot; &quot; is still required — :placeholder-shown is what tells the legend the field is filled.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

fieldset/legend notching is ancient and universal; :has() (via group-has-*) needs Chrome 105+, Safari 15.4+, Firefox 121+ — under the Tailwind floor anyway.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…