32 Tailwind CSS Tabs
11 / 32
Tailwind v4MIT licensed

Typewriter Animation Tabs in Tailwind CSS

Choosing a tab types the panel's line out character by character, then leaves a blinking amber caret. The original's steps(20) was a magic number that only fit one string; the port gives each line its own [--n:16] and animates to calc(var(--n)*1ch) in exactly steps(var(--n)), so any string types one glyph per step.

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

<section class="ttb-11 group/tabs grid min-h-svh w-full place-items-center bg-ttb11-bg px-5 py-14 font-ttb11-mono text-ttb11-ink" aria-label="Typewriter animation tabs demo">
  <div class="grid w-full max-w-lg justify-items-center gap-7">
    <div class="w-full rounded-2xl bg-ttb11-card p-2 ring-1 ring-white/8">
      <div role="radiogroup" aria-label="Message" class="flex gap-1 rounded-xl bg-black/25 p-1">
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r1" checked>Greet</label>
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r2">Status</label>
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r3">Goodbye</label>
      </div>
      <div class="flex min-h-24 items-center px-4 text-lg">
        <p class="hidden group-has-[#ttb11-r1:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:14] motion-reduce:animate-none">Hello, friend.</span></p>
        <p class="hidden group-has-[#ttb11-r2:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:16] motion-reduce:animate-none">Systems nominal.</span></p>
        <p class="hidden group-has-[#ttb11-r3:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:17] motion-reduce:animate-none">See you tomorrow.</span></p>
      </div>
    </div>
    <p aria-hidden="true" class="rounded-full border border-white/10 px-3.5 py-2 text-[11.5px] text-ttb11-mut">[--n:16] · w-[calc(var(--n)*1ch)] · steps(var(--n))</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-ttb11-bg: #17121f;
  --color-ttb11-bg: oklch(0.195 0.027 301.6);
  --color-ttb11-card: #211a2c;
  --color-ttb11-card: oklch(0.235 0.035 301.6);
  --color-ttb11-ink: #f3eff8;
  --color-ttb11-ink: oklch(0.958 0.013 306.0);
  --color-ttb11-mut: #a99fb8;
  --color-ttb11-mut: oklch(0.719 0.037 303.6);
  --color-ttb11-dim: #8b7f9c;
  --color-ttb11-dim: oklch(0.617 0.045 303.9);
  --color-ttb11-amber: #fbbf24;
  --color-ttb11-amber: oklch(0.837 0.164 84.4);
  --font-ttb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-ttb11-type: ttb11-type 1.2s steps(var(--n)) both, ttb11-blink 0.8s step-end 1.2s infinite;

  @keyframes ttb11-type {
    from {
      width: 0;
    }
  }

  @keyframes ttb11-blink {
    50% {
      border-color: transparent;
    }
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-11 {
  width: 100%;
  min-height: 100svh;
}

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-ttb11-bg: #17121f;
  --color-ttb11-bg: oklch(0.195 0.027 301.6);
  --color-ttb11-card: #211a2c;
  --color-ttb11-card: oklch(0.235 0.035 301.6);
  --color-ttb11-ink: #f3eff8;
  --color-ttb11-ink: oklch(0.958 0.013 306.0);
  --color-ttb11-mut: #a99fb8;
  --color-ttb11-mut: oklch(0.719 0.037 303.6);
  --color-ttb11-dim: #8b7f9c;
  --color-ttb11-dim: oklch(0.617 0.045 303.9);
  --color-ttb11-amber: #fbbf24;
  --color-ttb11-amber: oklch(0.837 0.164 84.4);
  --font-ttb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-ttb11-type: ttb11-type 1.2s steps(var(--n)) both, ttb11-blink 0.8s step-end 1.2s infinite;

  @keyframes ttb11-type {
    from {
      width: 0;
    }
  }

  @keyframes ttb11-blink {
    50% {
      border-color: transparent;
    }
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-11 {
  width: 100%;
  min-height: 100svh;
}
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=JetBrains+Mono:wght@400;500;600;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • steps(20) + width:100% became steps(var(--n)) + w-[calc(var(--n)*1ch)]: each string declares its own length, so typing is exactly one glyph per step for any copy.
  • Both keyframes live inside @theme next to a single --animate-ttb11-type token that chains them.
  • UI upgrade from the CSS original: a prompt glyph, an inset tab well with amber-tinted active state, and an 18px typed line (was 14px).
  • UI upgrade: resting labels raised from 45% white (≈3.1:1) to a 6.6:1 token; under reduced motion the full line shows with no caret flicker.
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: Typewriter Animation Tabs in Tailwind CSS
Source: https://codefronts.com/navigation/tailwind-css-tabs/typewriter-animation-tabs-tailwind-css/

Choosing a tab types the panel's line out character by character, then leaves a blinking amber caret. The original's steps(20) was a magic number that only fit one string; the port gives each line its own [--n:16] and animates to calc(var(--n)*1ch) in exactly steps(var(--n)), so any string types one glyph per step.
## HTML
```html
<section class="ttb-11 group/tabs grid min-h-svh w-full place-items-center bg-ttb11-bg px-5 py-14 font-ttb11-mono text-ttb11-ink" aria-label="Typewriter animation tabs demo">
  <div class="grid w-full max-w-lg justify-items-center gap-7">
    <div class="w-full rounded-2xl bg-ttb11-card p-2 ring-1 ring-white/8">
      <div role="radiogroup" aria-label="Message" class="flex gap-1 rounded-xl bg-black/25 p-1">
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r1" checked>Greet</label>
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r2">Status</label>
        <label class="flex min-h-10 flex-1 cursor-pointer items-center justify-center rounded-lg text-[11px] font-semibold tracking-[0.12em] text-ttb11-mut uppercase transition-colors hover:text-ttb11-ink has-checked:bg-ttb11-amber/12 has-checked:text-ttb11-amber has-focus-visible:outline-1 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed has-focus-visible:outline-ttb11-amber"><input class="sr-only" type="radio" name="ttb11" id="ttb11-r3">Goodbye</label>
      </div>
      <div class="flex min-h-24 items-center px-4 text-lg">
        <p class="hidden group-has-[#ttb11-r1:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:14] motion-reduce:animate-none">Hello, friend.</span></p>
        <p class="hidden group-has-[#ttb11-r2:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:16] motion-reduce:animate-none">Systems nominal.</span></p>
        <p class="hidden group-has-[#ttb11-r3:checked]/tabs:block"><span aria-hidden="true" class="mr-2 text-ttb11-dim">›</span><span class="inline-block w-[calc(var(--n)*1ch)] animate-ttb11-type overflow-hidden border-r-2 border-ttb11-amber align-bottom whitespace-nowrap text-ttb11-amber [--n:17] motion-reduce:animate-none">See you tomorrow.</span></p>
      </div>
    </div>
    <p aria-hidden="true" class="rounded-full border border-white/10 px-3.5 py-2 text-[11.5px] text-ttb11-mut">[--n:16] · w-[calc(var(--n)*1ch)] · steps(var(--n))</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttb11-bg: #17121f;
  --color-ttb11-bg: oklch(0.195 0.027 301.6);
  --color-ttb11-card: #211a2c;
  --color-ttb11-card: oklch(0.235 0.035 301.6);
  --color-ttb11-ink: #f3eff8;
  --color-ttb11-ink: oklch(0.958 0.013 306.0);
  --color-ttb11-mut: #a99fb8;
  --color-ttb11-mut: oklch(0.719 0.037 303.6);
  --color-ttb11-dim: #8b7f9c;
  --color-ttb11-dim: oklch(0.617 0.045 303.9);
  --color-ttb11-amber: #fbbf24;
  --color-ttb11-amber: oklch(0.837 0.164 84.4);
  --font-ttb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-ttb11-type: ttb11-type 1.2s steps(var(--n)) both, ttb11-blink 0.8s step-end 1.2s infinite;

  @keyframes ttb11-type {
    from {
      width: 0;
    }
  }

  @keyframes ttb11-blink {
    50% {
      border-color: transparent;
    }
  }
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-11 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Steps equal characters. In a monospace font 1ch is one glyph, so a width of n ch holds exactly n characters, and steps(n) reveals one per step:

<span class="[--n:14] w-[calc(var(--n)*1ch)] overflow-hidden whitespace-nowrap
  border-r-2 border-ttb11-amber animate-ttb11-type">Hello, friend.</span>

The keyframe only needs a start. from { width: 0 } animates to the element's own width, so one keyframe serves every string.

Free replay. A CSS animation restarts whenever its element leaves display:none — switching tabs types the new line again without a script.

Make it yours

  • Slower typing: raise 1.2s in the token; steps(var(--n)) keeps it one glyph per step.
  • Green CRT: amber → #4ade80 on near-black.
  • Multiple lines: give each line its own span and stagger with [animation-delay:…].

Gotchas — read before shipping

  • Set [--n] to the exact character count, including spaces and punctuation, or the caret stops early or late.
  • ch is only one glyph wide in a monospace font; a proportional font breaks the effect.
  • Long lines overflow narrow phones — keep strings under ~30 characters or allow horizontal scroll.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

var() inside steps() resolves at computed time in all engines; the floor is Tailwind v4's sheet.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Plain CSSno Tailwind surface

Search CodeFronts

Loading…