Tailwind v4MIT licensed

Tailwind ASCII Art Terminal Interface

A character-cell console on paper white rather than the usual black: box-drawn panels, a block-letter banner, and a working command line that takes input without a form element. The banner is aria-hidden with a real text alternative, because ASCII art read aloud is several hundred punctuation marks and no information.

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="twr-16 relative block w-full min-h-svh box-border bg-twr16-bg text-twr16-ink font-twr16-mono" aria-labelledby="twr-16-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-16-title">ASCII art terminal console</h2>

  <div class="absolute top-4 right-4 z-6">
    <input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-16-theme">
    <label class="grid place-items-center size-11 border border-twr16-line bg-twr16-paper text-twr16-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr16-acc peer-focus-visible:outline-offset-2" for="twr-16-theme">
      <svg class="size-5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark console</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-4 max-w-[64rem] mx-auto px-[clamp(0.6rem,0.4rem+1.5vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
    <p class="m-0 justify-self-start min-w-0 border border-twr16-line bg-twr16-paper px-2.5 py-1.5 text-[0.68rem] tracking-[0.12em] uppercase text-twr16-ink2">Character-cell console &middot; code page 437 frames &middot; ink on paper</p>

    <div class="min-w-0 grid gap-4 border border-twr16-line bg-twr16-paper p-[clamp(0.8rem,0.6rem+1.2vw,1.5rem)]">
      <div class="min-w-0">
        <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-none text-twr16-ink text-[calc(var(--twr-16-cell)*0.9)]" aria-hidden="true">  ████      ██████    ██████  ██████  ██████
██    ██  ██        ██          ██      ██  
████████    ████    ██          ██      ██  
██    ██        ██  ██          ██      ██  
██    ██  ██████      ██████  ██████  ██████</pre>
        <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Block-letter banner reading ASCII, drawn in full-block characters.</p>
      </div>

      <div class="grid gap-4 min-w-0 md:grid-cols-2">
        <div class="min-w-0">
          <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-[1.25] text-twr16-ink text-[var(--twr-16-cell)]" aria-hidden="true">┌─ SESSION ──────────────────┐
│ host   northgate.local     │
│ tty    /dev/ttyS0          │
│ rate   9600 8N1            │
│ codec  cp437               │
└────────────────────────────┘</pre>
          <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Session panel: host northgate.local, tty /dev/ttyS0, rate 9600 8N1, codec cp437.</p>
        </div>
        <div class="min-w-0">
          <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-[1.25] text-twr16-ink text-[var(--twr-16-cell)]" aria-hidden="true">╔═ COMMANDS ═════════════════╗
║ help    list commands      ║
║ ver     print version      ║
║ dir     list files         ║
║ clear   empty the log      ║
╚════════════════════════════╝</pre>
          <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Commands panel: help lists commands, ver prints the version, dir lists files, clear empties the log.</p>
        </div>
      </div>

      <ul class="list-none m-0 p-3 min-w-0 grid gap-1 min-h-[9rem] max-h-[16rem] overflow-y-auto border border-twr16-line bg-twr16-bg text-[var(--twr-16-cell)] leading-relaxed" data-twr-16-log aria-live="polite" aria-relevant="additions" aria-label="Console output">
        <li class="min-w-0 [overflow-wrap:anywhere] text-twr16-ink">northgate console ready. type <b class="font-bold text-twr16-acc">help</b> and press Enter.</li>
      </ul>

      <template data-twr-16-line>
        <li class="min-w-0 [overflow-wrap:anywhere] text-twr16-ink data-[kind=err]:text-twr16-acc data-[kind=dim]:text-twr16-ink2"></li>
      </template>

      <div class="flex flex-wrap items-center gap-2 min-w-0">
        <label class="font-bold text-twr16-acc text-[var(--twr-16-cell)]" for="twr-16-input">C:\&gt;</label>
        <input class="flex-1 min-w-0 basis-40 min-h-11 px-2.5 py-2 border border-twr16-line bg-twr16-bg text-twr16-ink font-inherit text-[var(--twr-16-cell)] focus-visible:outline-2 focus-visible:outline-twr16-acc focus-visible:outline-offset-[-2px]" id="twr-16-input" type="text" data-twr-16-input autocomplete="off" spellcheck="false" placeholder="help">
        <button class="min-h-11 min-w-11 px-4 py-2 border border-twr16-ink bg-twr16-ink text-twr16-paper font-inherit text-[0.75rem] tracking-[0.12em] uppercase cursor-pointer focus-visible:outline-2 focus-visible:outline-twr16-acc focus-visible:outline-offset-2" type="button" data-twr-16-run>Run</button>
      </div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-twr16-paper: #f6f4ec;
  --color-twr16-paper: oklch(0.97 0.008 96);
  --color-twr16-ink: #1a1a16;
  --color-twr16-ink: oklch(0.21 0.008 96);
  --color-twr16-ink2: #5a584e;
  --color-twr16-ink2: oklch(0.47 0.012 96);
  --color-twr16-line: #b6b3a5;
  --color-twr16-line: oklch(0.76 0.014 96);
  --color-twr16-bg: #e2dfd4;
  --color-twr16-bg: oklch(0.90 0.012 96);
  --color-twr16-acc: #8a3a08;
  --color-twr16-acc: oklch(0.43 0.13 55);
  --font-twr16-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}

.twr-16 {
  --twr-16-cell: clamp(0.58rem, 0.42rem + 0.85vw, 0.85rem);
}

.twr-16:has(#twr-16-theme:checked) {
  --color-twr16-paper: #16160f;
  --color-twr16-paper: oklch(0.20 0.01 96);
  --color-twr16-ink: #f0eee2;
  --color-twr16-ink: oklch(0.95 0.008 96);
  --color-twr16-ink2: #aaa799;
  --color-twr16-ink2: oklch(0.73 0.013 96);
  --color-twr16-line: #464438;
  --color-twr16-line: oklch(0.38 0.014 96);
  --color-twr16-bg: #0e0e09;
  --color-twr16-bg: oklch(0.16 0.008 96);
  --color-twr16-acc: #ff9a52;
  --color-twr16-acc: oklch(0.78 0.13 55);
}
/* No form submit: a sandboxed iframe without form permission never fires one.
   Lines are cloned from the template so every utility exists in the source. */
(function () {
  var root = document.querySelector('.twr-16');
  if (!root) return;
  var log = root.querySelector('[data-twr-16-log]');
  var input = root.querySelector('[data-twr-16-input]');
  var run = root.querySelector('[data-twr-16-run]');
  var tpl = root.querySelector('[data-twr-16-line]');

  var replies = {
    help: ['help  ver  dir  clear', 'dim'],
    ver: ['northgate console 2.4 (cp437)', ''],
    dir: ['AUTOEXEC.BAT  CONFIG.SYS  README.TXT', '']
  };

  function print(text, kind) {
    var li = tpl.content.firstElementChild.cloneNode(true);
    if (kind) li.setAttribute('data-kind', kind);
    li.textContent = text;
    log.appendChild(li);
    while (log.childElementCount > 40) log.removeChild(log.firstElementChild);
    log.scrollTop = log.scrollHeight;
  }

  function submit() {
    var cmd = input.value.trim().toLowerCase();
    if (!cmd) return;
    print('C:\> ' + cmd, 'dim');
    if (cmd === 'clear') {
      log.textContent = '';
    } else if (replies[cmd]) {
      print(replies[cmd][0], replies[cmd][1]);
    } else {
      print('bad command or file name: ' + cmd, 'err');
    }
    input.value = '';
  }

  function onKey(e) { if (e.key === 'Enter') { e.preventDefault(); submit(); } }
  run.addEventListener('click', submit);
  input.addEventListener('keydown', onKey);

  root.__twrCleanup = function () {
    run.removeEventListener('click', submit);
    input.removeEventListener('keydown', onKey);
  };
})();

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-twr16-paper: #f6f4ec;
  --color-twr16-paper: oklch(0.97 0.008 96);
  --color-twr16-ink: #1a1a16;
  --color-twr16-ink: oklch(0.21 0.008 96);
  --color-twr16-ink2: #5a584e;
  --color-twr16-ink2: oklch(0.47 0.012 96);
  --color-twr16-line: #b6b3a5;
  --color-twr16-line: oklch(0.76 0.014 96);
  --color-twr16-bg: #e2dfd4;
  --color-twr16-bg: oklch(0.90 0.012 96);
  --color-twr16-acc: #8a3a08;
  --color-twr16-acc: oklch(0.43 0.13 55);
  --font-twr16-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}

.twr-16 {
  --twr-16-cell: clamp(0.58rem, 0.42rem + 0.85vw, 0.85rem);
}

.twr-16:has(#twr-16-theme:checked) {
  --color-twr16-paper: #16160f;
  --color-twr16-paper: oklch(0.20 0.01 96);
  --color-twr16-ink: #f0eee2;
  --color-twr16-ink: oklch(0.95 0.008 96);
  --color-twr16-ink2: #aaa799;
  --color-twr16-ink2: oklch(0.73 0.013 96);
  --color-twr16-line: #464438;
  --color-twr16-line: oklch(0.38 0.014 96);
  --color-twr16-bg: #0e0e09;
  --color-twr16-bg: oklch(0.16 0.008 96);
  --color-twr16-acc: #ff9a52;
  --color-twr16-acc: oklch(0.78 0.13 55);
}
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;700&display=swap">

What changed in the port

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

  • The webfonts load as <link> tags in setup, not as an @import in the stylesheet — Tailwind emits user @import rules after its own layers, where the browser drops them.
  • This is the demo that teaches the compiler's one hard rule: a class assembled in JavaScript does not exist. Output lines are cloned from a <template> and switched with data-[kind=...] variants that appear literally in the source.
  • No form element anywhere: a button click plus an Enter keydown, because a sandboxed iframe without form permission never fires submit.
  • The live region is the log alone, with aria-relevant=&quot;additions&quot;, so a command announces one line rather than replaying the session.
  • Every art block is aria-hidden with a visually hidden description immediately after it — and the description has to be updated whenever the art is.
  • --twr-16-cell is read by text-[var(--twr-16-cell)] on each <pre>, so the whole character grid scales from one clamp.
  • Each <pre> carries overflow-x-auto, so a 30-column frame scrolls itself rather than widening the page.
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 ASCII Art Terminal Interface
Source: https://codefronts.com/design-styles/tailwind-retro-ui/tailwind-ascii-art-terminal-interface/

A character-cell console on paper white rather than the usual black: box-drawn panels, a block-letter banner, and a working command line that takes input without a form element. The banner is aria-hidden with a real text alternative, because ASCII art read aloud is several hundred punctuation marks and no information.
## HTML
```html
<section class="twr-16 relative block w-full min-h-svh box-border bg-twr16-bg text-twr16-ink font-twr16-mono" aria-labelledby="twr-16-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-16-title">ASCII art terminal console</h2>

  <div class="absolute top-4 right-4 z-6">
    <input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-16-theme">
    <label class="grid place-items-center size-11 border border-twr16-line bg-twr16-paper text-twr16-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr16-acc peer-focus-visible:outline-offset-2" for="twr-16-theme">
      <svg class="size-5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark console</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-4 max-w-[64rem] mx-auto px-[clamp(0.6rem,0.4rem+1.5vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
    <p class="m-0 justify-self-start min-w-0 border border-twr16-line bg-twr16-paper px-2.5 py-1.5 text-[0.68rem] tracking-[0.12em] uppercase text-twr16-ink2">Character-cell console &middot; code page 437 frames &middot; ink on paper</p>

    <div class="min-w-0 grid gap-4 border border-twr16-line bg-twr16-paper p-[clamp(0.8rem,0.6rem+1.2vw,1.5rem)]">
      <div class="min-w-0">
        <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-none text-twr16-ink text-[calc(var(--twr-16-cell)*0.9)]" aria-hidden="true">  ████      ██████    ██████  ██████  ██████
██    ██  ██        ██          ██      ██  
████████    ████    ██          ██      ██  
██    ██        ██  ██          ██      ██  
██    ██  ██████      ██████  ██████  ██████</pre>
        <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Block-letter banner reading ASCII, drawn in full-block characters.</p>
      </div>

      <div class="grid gap-4 min-w-0 md:grid-cols-2">
        <div class="min-w-0">
          <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-[1.25] text-twr16-ink text-[var(--twr-16-cell)]" aria-hidden="true">┌─ SESSION ──────────────────┐
│ host   northgate.local     │
│ tty    /dev/ttyS0          │
│ rate   9600 8N1            │
│ codec  cp437               │
└────────────────────────────┘</pre>
          <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Session panel: host northgate.local, tty /dev/ttyS0, rate 9600 8N1, codec cp437.</p>
        </div>
        <div class="min-w-0">
          <pre class="m-0 min-w-0 overflow-x-auto whitespace-pre font-inherit leading-[1.25] text-twr16-ink text-[var(--twr-16-cell)]" aria-hidden="true">╔═ COMMANDS ═════════════════╗
║ help    list commands      ║
║ ver     print version      ║
║ dir     list files         ║
║ clear   empty the log      ║
╚════════════════════════════╝</pre>
          <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Commands panel: help lists commands, ver prints the version, dir lists files, clear empties the log.</p>
        </div>
      </div>

      <ul class="list-none m-0 p-3 min-w-0 grid gap-1 min-h-[9rem] max-h-[16rem] overflow-y-auto border border-twr16-line bg-twr16-bg text-[var(--twr-16-cell)] leading-relaxed" data-twr-16-log aria-live="polite" aria-relevant="additions" aria-label="Console output">
        <li class="min-w-0 [overflow-wrap:anywhere] text-twr16-ink">northgate console ready. type <b class="font-bold text-twr16-acc">help</b> and press Enter.</li>
      </ul>

      <template data-twr-16-line>
        <li class="min-w-0 [overflow-wrap:anywhere] text-twr16-ink data-[kind=err]:text-twr16-acc data-[kind=dim]:text-twr16-ink2"></li>
      </template>

      <div class="flex flex-wrap items-center gap-2 min-w-0">
        <label class="font-bold text-twr16-acc text-[var(--twr-16-cell)]" for="twr-16-input">C:\&gt;</label>
        <input class="flex-1 min-w-0 basis-40 min-h-11 px-2.5 py-2 border border-twr16-line bg-twr16-bg text-twr16-ink font-inherit text-[var(--twr-16-cell)] focus-visible:outline-2 focus-visible:outline-twr16-acc focus-visible:outline-offset-[-2px]" id="twr-16-input" type="text" data-twr-16-input autocomplete="off" spellcheck="false" placeholder="help">
        <button class="min-h-11 min-w-11 px-4 py-2 border border-twr16-ink bg-twr16-ink text-twr16-paper font-inherit text-[0.75rem] tracking-[0.12em] uppercase cursor-pointer focus-visible:outline-2 focus-visible:outline-twr16-acc focus-visible:outline-offset-2" type="button" data-twr-16-run>Run</button>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-twr16-paper: #f6f4ec;
  --color-twr16-paper: oklch(0.97 0.008 96);
  --color-twr16-ink: #1a1a16;
  --color-twr16-ink: oklch(0.21 0.008 96);
  --color-twr16-ink2: #5a584e;
  --color-twr16-ink2: oklch(0.47 0.012 96);
  --color-twr16-line: #b6b3a5;
  --color-twr16-line: oklch(0.76 0.014 96);
  --color-twr16-bg: #e2dfd4;
  --color-twr16-bg: oklch(0.90 0.012 96);
  --color-twr16-acc: #8a3a08;
  --color-twr16-acc: oklch(0.43 0.13 55);
  --font-twr16-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}

.twr-16 {
  --twr-16-cell: clamp(0.58rem, 0.42rem + 0.85vw, 0.85rem);
}

.twr-16:has(#twr-16-theme:checked) {
  --color-twr16-paper: #16160f;
  --color-twr16-paper: oklch(0.20 0.01 96);
  --color-twr16-ink: #f0eee2;
  --color-twr16-ink: oklch(0.95 0.008 96);
  --color-twr16-ink2: #aaa799;
  --color-twr16-ink2: oklch(0.73 0.013 96);
  --color-twr16-line: #464438;
  --color-twr16-line: oklch(0.38 0.014 96);
  --color-twr16-bg: #0e0e09;
  --color-twr16-bg: oklch(0.16 0.008 96);
  --color-twr16-acc: #ff9a52;
  --color-twr16-acc: oklch(0.78 0.13 55);
}
```

## JavaScript
```js
/* No form submit: a sandboxed iframe without form permission never fires one.
   Lines are cloned from the template so every utility exists in the source. */
(function () {
  var root = document.querySelector('.twr-16');
  if (!root) return;
  var log = root.querySelector('[data-twr-16-log]');
  var input = root.querySelector('[data-twr-16-input]');
  var run = root.querySelector('[data-twr-16-run]');
  var tpl = root.querySelector('[data-twr-16-line]');

  var replies = {
    help: ['help  ver  dir  clear', 'dim'],
    ver: ['northgate console 2.4 (cp437)', ''],
    dir: ['AUTOEXEC.BAT  CONFIG.SYS  README.TXT', '']
  };

  function print(text, kind) {
    var li = tpl.content.firstElementChild.cloneNode(true);
    if (kind) li.setAttribute('data-kind', kind);
    li.textContent = text;
    log.appendChild(li);
    while (log.childElementCount > 40) log.removeChild(log.firstElementChild);
    log.scrollTop = log.scrollHeight;
  }

  function submit() {
    var cmd = input.value.trim().toLowerCase();
    if (!cmd) return;
    print('C:\> ' + cmd, 'dim');
    if (cmd === 'clear') {
      log.textContent = '';
    } else if (replies[cmd]) {
      print(replies[cmd][0], replies[cmd][1]);
    } else {
      print('bad command or file name: ' + cmd, 'err');
    }
    input.value = '';
  }

  function onKey(e) { if (e.key === 'Enter') { e.preventDefault(); submit(); } }
  run.addEventListener('click', submit);
  input.addEventListener('keydown', onKey);

  root.__twrCleanup = function () {
    run.removeEventListener('click', submit);
    input.removeEventListener('keydown', onKey);
  };
})();
```

How this works

ASCII art is decoration and must be marked as such. A screen reader handed a block-letter banner reads out several hundred full-block characters, or spells the punctuation. The fix is two lines — aria-hidden="true" on the art and a visually hidden element saying what it depicts — and it takes less effort than the banner did.

The console cannot use a form. Demo pages here run inside an iframe with scripts allowed and forms not allowed, so a submit event never fires and a form-based command line silently does nothing. The working pattern is a plain input, a button with a click handler, and a keydown listener treating Enter as the same action — also more robust generally, because it does not rely on implicit submission behaviour.

The one Tailwind-specific hazard: dynamically built classes do not exist. A console appends lines at runtime, and the instinct is to write el.className = 'text-twr16-' + kind. That compiles to nothing, because the compiler scans source text and never sees the concatenated string. Two fixes, both used here: clone a <template> that already carries the utilities, and switch appearance with data-kind attributes plus data-kind-err:-style variants that are written literally in the template. The rule: every class must appear, spelled out, somewhere in the source.

<template data-twr-16-line>
  <li class="text-twr16-ink data-[kind=err]:text-twr16-acc
             data-[kind=dim]:text-twr16-ink2"></li>
</template>

The live region has to be narrow. Marking the whole console as a live region replays the entire history on every command. The region is the log element with aria-relevant="additions"; the banner and prompt sit outside it. Get this wrong and the interface is technically accessible and practically unusable.

A light-ground terminal is a real thing, not a modern reinterpretation. Printing terminals put dark ink on paper and plenty of later console software shipped a light scheme. The box-drawing characters carry the era; the ground does not have to.

Make it yours

  • Change --twr-16-cell on the section to rescale the whole character grid; the frames are characters, so they scale with it.
  • Swap the ink and paper tokens for a dark console; nothing in the layout assumes a light ground.
  • Replace the banner with new block-letter art and update the hidden description to match — never leave the old description in place.
  • Extend the command set in the script's responses map; unknown commands already fall through to a not-found line.
  • Change the frames from double-line to single-line by swapping characters; both occupy one cell.
  • Add an output kind by adding one data-[kind=...] variant to the template line and using that value from the script.

Gotchas — read before shipping

  • Never build a class name in JavaScript. 'text-twr16-' + kind compiles to nothing — put the utilities in a template and switch them with data-* variants.
  • ASCII art without aria-hidden and a text alternative is read aloud as a wall of punctuation.
  • A form submit handler never fires in a sandboxed iframe without form permission. Use a button click plus an Enter keydown.
  • Marking the whole log as a live region replays the entire history on every command. Scope the region and announce additions only.
  • Box-drawing glyphs are not guaranteed to share an advance width across every fallback font. If a frame shears, the stack substituted a glyph from a non-monospace face.
  • <pre> needs overflow-x-auto at narrow widths, or the frames force the whole page to scroll horizontally.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

The ch unit, CSS grid and attribute selectors are Baseline since 2017, and the script is plain ES2020. Tailwind v4's generated sheet sets the floor at Chrome 111+, Safari 16.4+, Firefox 128+.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Arbitraryneeds bracket syntax

Search CodeFronts

Loading…