Tailwind v4MIT licensed

Tailwind MS DOS Text Mode Interface Grid

A character-cell interface reproduced as one: an 80×25 field where every cell is exactly one monospace advance, framed with code page 437 box-drawing characters and restricted to the 16-colour CGA palette as theme tokens. The constraint is the subject — nothing can sit between two cells — and Tailwind's container-query utilities turn out to be the cleanest way yet to make 80 columns fit their frame exactly.

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-06 relative block w-full min-h-svh box-border bg-twr06-bg text-twr06-ink font-twr06-mono" aria-labelledby="twr-06-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-06-title">MS-DOS text mode character grid</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-06-theme">
    <label class="grid place-items-center size-11 border border-twr06-line bg-twr06-surface text-twr06-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr06-acc peer-focus-visible:outline-offset-2" for="twr-06-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 surround</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-4.5 max-w-[72rem] mx-auto px-[clamp(0.6rem,0.4rem+1.5vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16 lg:grid-cols-2">
    <p class="m-0 justify-self-start min-w-0 lg:col-span-2 border border-twr06-line bg-twr06-surface px-2.5 py-1.5 text-[0.68rem] tracking-[0.12em] uppercase text-twr06-ink2">IBM CGA &middot; 16 colours &middot; 80 &times; 25 text mode &middot; code page 437</p>

    <div class="min-w-0 lg:col-span-2 rounded-md bg-[#6d6f70] p-[clamp(0.5rem,0.3rem+1vw,1rem)] [box-shadow:inset_0_1px_0_rgba(255,255,255,0.35),inset_0_-2px_0_rgba(0,0,0,0.4),0_14px_30px_-16px_rgba(0,0,0,0.6)]">
      <div class="@container min-w-0 overflow-hidden grid grid-cols-[minmax(0,1fr)] content-start border-2 border-[#1b1d1f] bg-twr06-blue text-twr06-ltgrey">
        <div class="flex items-center min-h-[1.6em] px-[0.5ch] min-w-0 overflow-hidden whitespace-nowrap bg-twr06-ltgrey text-twr06-k text-[clamp(0.66rem,0.55rem+0.6vw,0.85rem)]" role="presentation">
          <span class="font-bold text-twr06-red">F</span><span>ile</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">E</span><span>dit</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">S</span><span>earch</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">O</span><span>ptions</span>
          <span class="ms-auto ps-[1ch] text-[#303030]">MEM 640K</span>
        </div>

        <pre class="twr-06__art hidden md:block m-0 py-2 min-w-0 overflow-hidden font-inherit whitespace-pre text-center leading-[1.16] text-twr06-ltcyan text-[min(0.95rem,2.02cqi)]" aria-hidden="true">╔══════════════════════════════════════════════════════════════════════════════╗
║  C:\ ─ Directory of drive C  ─  6 file(s)  ─  90,671 bytes                   ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Name            Size      Date        Attr    Type                          ║
║  ────────────────────────────────────────────────────────────────────        ║
║  AUTOEXEC.BAT      1,024   04-12-1995  A       Batch file                    ║
║  COMMAND.COM      54,619   05-31-1994  RA      Command interpreter           ║
║  CONFIG.SYS          412   04-12-1995  A       System configuration          ║
║  HIMEM.SYS        29,136   05-31-1994  RA      Extended memory manager       ║
║  README.TXT        7,880   11-03-1995  A       Text                          ║
║                                                                              ║
║  Every glyph above occupies exactly one cell. Nothing sits between two cells.║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
╚══════════════════════════════════════════════════════════════════════════════╝</pre>
        <pre class="twr-06__art block md:hidden m-0 py-2 min-w-0 overflow-hidden font-inherit whitespace-pre text-center leading-[1.16] text-twr06-ltcyan text-[min(0.95rem,4.1cqi)]" aria-hidden="true">╔══════════════════════════════════════╗
║  C:\ ─ 6 file(s), 90,671 bytes       ║
╠══════════════════════════════════════╣
║ Name          Size    Attr           ║
║ ─────────────────────────────────────║
║ AUTOEXEC.BAT  1,024   A              ║
║ COMMAND.COM  54,619   RA             ║
║ CONFIG.SYS      412   A              ║
║ HIMEM.SYS    29,136   RA             ║
║ README.TXT    7,880   A              ║
║                                      ║
║  One glyph = one cell. No between.   ║
║                                      ║
║                                      ║
╚══════════════════════════════════════╝</pre>
        <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">A DOS-style file manager panel listing AUTOEXEC.BAT, CONFIG.SYS, COMMAND.COM and HIMEM.SYS with sizes and dates, framed in double-line box characters.</p>

        <div class="flex flex-wrap items-center gap-x-[2ch] min-h-[1.6em] px-[0.5ch] min-w-0 bg-twr06-ltgrey text-twr06-k text-[clamp(0.66rem,0.55rem+0.6vw,0.85rem)]">
          <span><span class="font-bold text-twr06-red">F1</span> Help</span>
          <span><span class="font-bold text-twr06-red">F3</span> Open</span>
          <span><span class="font-bold text-twr06-red">F10</span> Menu</span>
          <span class="ms-auto text-[#303030]">80 &times; 25</span>
        </div>
      </div>
    </div>

    <aside class="min-w-0 grid content-start gap-2.5 border border-twr06-line bg-twr06-surface p-[clamp(0.8rem,0.6rem+0.8vw,1.1rem)]">
      <h3 class="m-0 text-[0.72rem] tracking-[0.1em] uppercase text-twr06-ink2">Cell inspector &mdash; the grid the text lands on</h3>
      <div class="grid grid-cols-12 gap-px min-w-0 border border-twr06-line bg-twr06-line text-[clamp(0.8rem,0.6rem+1.4vw,1.35rem)]" aria-hidden="true">
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╔</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╦</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╗</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">█</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">▓</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">▒</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">░</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">A</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">B</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">C</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">D</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">─</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┼</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">─</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┤</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╚</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╩</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╝</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">└</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┴</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┘</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
      </div>
      <p class="m-0 min-w-0 text-[0.8rem] leading-relaxed text-pretty text-twr06-ink2">Each box is <code class="bg-twr06-bg px-1">1ch</code> wide and one line-height tall. The frame is characters, not borders &mdash; a border would have to live between two cells, and there is no between.</p>
    </aside>

    <aside class="min-w-0 grid content-start gap-2.5 border border-twr06-line bg-twr06-surface p-[clamp(0.8rem,0.6rem+0.8vw,1.1rem)]">
      <h3 class="m-0 text-[0.72rem] tracking-[0.1em] uppercase text-twr06-ink2">CGA palette &mdash; 16 defined values</h3>
      <ul class="list-none m-0 p-0 min-w-0 grid gap-0.5 grid-cols-[repeat(auto-fit,minmax(min(100%,7.5rem),1fr))]">
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#000000]">0 black</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#0000aa]">1 blue</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#00aa00]">2 green</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#00aaaa]">3 cyan</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa0000]">4 red</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa00aa]">5 magenta</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa5500]">6 brown</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aaaaaa]">7 lt grey</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#555555]">8 dk grey</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#5555ff]">9 lt blue</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#55ff55]">10 lt green</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#55ffff]">11 lt cyan</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ff5555]">12 lt red</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ff55ff]">13 lt magenta</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ffff55]">14 yellow</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ffffff]">15 white</li>
      </ul>
    </aside>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-twr06-k: #000000;
  --color-twr06-blue: #0000aa;
  --color-twr06-cyan: #00aaaa;
  --color-twr06-ltgrey: #aaaaaa;
  --color-twr06-dkgrey: #555555;
  --color-twr06-ltcyan: #55ffff;
  --color-twr06-yellow: #ffff55;
  --color-twr06-white: #ffffff;
  --color-twr06-red: #aa0000;
  --color-twr06-bg: #c9ccd2;
  --color-twr06-bg: oklch(0.84 0.008 260);
  --color-twr06-surface: #eef0f3;
  --color-twr06-surface: oklch(0.95 0.004 260);
  --color-twr06-ink: #14171c;
  --color-twr06-ink: oklch(0.19 0.008 260);
  --color-twr06-ink2: #464e58;
  --color-twr06-ink2: oklch(0.42 0.014 260);
  --color-twr06-line: #9aa1ab;
  --color-twr06-line: oklch(0.71 0.012 260);
  --color-twr06-acc: #1247c9;
  --color-twr06-acc: oklch(0.45 0.2 264);
  --font-twr06-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}
/* A feature query that substitutes the very property it tests cannot be a
   variant — the utility and the fallback would compete in the same layer. */

@supports not (font-size: 1cqi) {
  .twr-06__art {
    font-size: clamp(0.35rem, 1.6vw, 0.95rem);
  }
}

.twr-06:has(#twr-06-theme:checked) {
  --color-twr06-bg: #101318;
  --color-twr06-bg: oklch(0.18 0.008 260);
  --color-twr06-surface: #1a1e25;
  --color-twr06-surface: oklch(0.24 0.01 260);
  --color-twr06-ink: #eef1f5;
  --color-twr06-ink: oklch(0.96 0.004 260);
  --color-twr06-ink2: #a3abb6;
  --color-twr06-ink2: oklch(0.74 0.012 260);
  --color-twr06-line: #39414c;
  --color-twr06-line: oklch(0.35 0.014 260);
  --color-twr06-acc: #7fa8ff;
  --color-twr06-acc: oklch(0.76 0.13 264);
}

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-twr06-k: #000000;
  --color-twr06-blue: #0000aa;
  --color-twr06-cyan: #00aaaa;
  --color-twr06-ltgrey: #aaaaaa;
  --color-twr06-dkgrey: #555555;
  --color-twr06-ltcyan: #55ffff;
  --color-twr06-yellow: #ffff55;
  --color-twr06-white: #ffffff;
  --color-twr06-red: #aa0000;
  --color-twr06-bg: #c9ccd2;
  --color-twr06-bg: oklch(0.84 0.008 260);
  --color-twr06-surface: #eef0f3;
  --color-twr06-surface: oklch(0.95 0.004 260);
  --color-twr06-ink: #14171c;
  --color-twr06-ink: oklch(0.19 0.008 260);
  --color-twr06-ink2: #464e58;
  --color-twr06-ink2: oklch(0.42 0.014 260);
  --color-twr06-line: #9aa1ab;
  --color-twr06-line: oklch(0.71 0.012 260);
  --color-twr06-acc: #1247c9;
  --color-twr06-acc: oklch(0.45 0.2 264);
  --font-twr06-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}
/* A feature query that substitutes the very property it tests cannot be a
   variant — the utility and the fallback would compete in the same layer. */

@supports not (font-size: 1cqi) {
  .twr-06__art {
    font-size: clamp(0.35rem, 1.6vw, 0.95rem);
  }
}

.twr-06:has(#twr-06-theme:checked) {
  --color-twr06-bg: #101318;
  --color-twr06-bg: oklch(0.18 0.008 260);
  --color-twr06-surface: #1a1e25;
  --color-twr06-surface: oklch(0.24 0.01 260);
  --color-twr06-ink: #eef1f5;
  --color-twr06-ink: oklch(0.96 0.004 260);
  --color-twr06-ink2: #a3abb6;
  --color-twr06-ink2: oklch(0.74 0.012 260);
  --color-twr06-line: #39414c;
  --color-twr06-line: oklch(0.35 0.014 260);
  --color-twr06-acc: #7fa8ff;
  --color-twr06-acc: oklch(0.76 0.13 264);
}
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.
  • @container plus text-[min(0.95rem,2.02cqi)] is the port's best moment: the container and the query unit that depends on it are two classes on two elements, visible together.
  • Character-relative units stay arbitrary values (px-[0.5ch], gap-x-[2ch], min-h-[1.6em]). The design scale here is the character cell, which is not a spacing ramp and should not be themed as one.
  • The 40-column fallback is a plain hidden md:block / md:hidden pair, and each <pre> carries its own cqi coefficient — two fields, two clocks, no media query.
  • Palette swatches use per-item before:bg-[#hex] rather than sixteen tokens, because each value appears once. Tokens are for reuse, not for inventory.
  • The nine palette entries the chrome actually uses are tokens, so the screen can be reskinned by swapping which token each element names.
  • The only CSS left is the @supports fallback and the surround flip — the character grid itself is entirely utilities.
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 MS DOS Text Mode Interface Grid
Source: https://codefronts.com/design-styles/tailwind-retro-ui/tailwind-ms-dos-text-mode-interface-grid/

A character-cell interface reproduced as one: an 80&times;25 field where every cell is exactly one monospace advance, framed with code page 437 box-drawing characters and restricted to the 16-colour CGA palette as theme tokens. The constraint is the subject &mdash; nothing can sit between two cells &mdash; and Tailwind's container-query utilities turn out to be the cleanest way yet to make 80 columns fit their frame exactly.
## HTML
```html
<section class="twr-06 relative block w-full min-h-svh box-border bg-twr06-bg text-twr06-ink font-twr06-mono" aria-labelledby="twr-06-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-06-title">MS-DOS text mode character grid</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-06-theme">
    <label class="grid place-items-center size-11 border border-twr06-line bg-twr06-surface text-twr06-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr06-acc peer-focus-visible:outline-offset-2" for="twr-06-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 surround</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-4.5 max-w-[72rem] mx-auto px-[clamp(0.6rem,0.4rem+1.5vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16 lg:grid-cols-2">
    <p class="m-0 justify-self-start min-w-0 lg:col-span-2 border border-twr06-line bg-twr06-surface px-2.5 py-1.5 text-[0.68rem] tracking-[0.12em] uppercase text-twr06-ink2">IBM CGA &middot; 16 colours &middot; 80 &times; 25 text mode &middot; code page 437</p>

    <div class="min-w-0 lg:col-span-2 rounded-md bg-[#6d6f70] p-[clamp(0.5rem,0.3rem+1vw,1rem)] [box-shadow:inset_0_1px_0_rgba(255,255,255,0.35),inset_0_-2px_0_rgba(0,0,0,0.4),0_14px_30px_-16px_rgba(0,0,0,0.6)]">
      <div class="@container min-w-0 overflow-hidden grid grid-cols-[minmax(0,1fr)] content-start border-2 border-[#1b1d1f] bg-twr06-blue text-twr06-ltgrey">
        <div class="flex items-center min-h-[1.6em] px-[0.5ch] min-w-0 overflow-hidden whitespace-nowrap bg-twr06-ltgrey text-twr06-k text-[clamp(0.66rem,0.55rem+0.6vw,0.85rem)]" role="presentation">
          <span class="font-bold text-twr06-red">F</span><span>ile</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">E</span><span>dit</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">S</span><span>earch</span>
          <span class="flex-none w-[2ch]"></span>
          <span class="font-bold text-twr06-red">O</span><span>ptions</span>
          <span class="ms-auto ps-[1ch] text-[#303030]">MEM 640K</span>
        </div>

        <pre class="twr-06__art hidden md:block m-0 py-2 min-w-0 overflow-hidden font-inherit whitespace-pre text-center leading-[1.16] text-twr06-ltcyan text-[min(0.95rem,2.02cqi)]" aria-hidden="true">╔══════════════════════════════════════════════════════════════════════════════╗
║  C:\ ─ Directory of drive C  ─  6 file(s)  ─  90,671 bytes                   ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Name            Size      Date        Attr    Type                          ║
║  ────────────────────────────────────────────────────────────────────        ║
║  AUTOEXEC.BAT      1,024   04-12-1995  A       Batch file                    ║
║  COMMAND.COM      54,619   05-31-1994  RA      Command interpreter           ║
║  CONFIG.SYS          412   04-12-1995  A       System configuration          ║
║  HIMEM.SYS        29,136   05-31-1994  RA      Extended memory manager       ║
║  README.TXT        7,880   11-03-1995  A       Text                          ║
║                                                                              ║
║  Every glyph above occupies exactly one cell. Nothing sits between two cells.║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
║                                                                              ║
╚══════════════════════════════════════════════════════════════════════════════╝</pre>
        <pre class="twr-06__art block md:hidden m-0 py-2 min-w-0 overflow-hidden font-inherit whitespace-pre text-center leading-[1.16] text-twr06-ltcyan text-[min(0.95rem,4.1cqi)]" aria-hidden="true">╔══════════════════════════════════════╗
║  C:\ ─ 6 file(s), 90,671 bytes       ║
╠══════════════════════════════════════╣
║ Name          Size    Attr           ║
║ ─────────────────────────────────────║
║ AUTOEXEC.BAT  1,024   A              ║
║ COMMAND.COM  54,619   RA             ║
║ CONFIG.SYS      412   A              ║
║ HIMEM.SYS    29,136   RA             ║
║ README.TXT    7,880   A              ║
║                                      ║
║  One glyph = one cell. No between.   ║
║                                      ║
║                                      ║
╚══════════════════════════════════════╝</pre>
        <p class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">A DOS-style file manager panel listing AUTOEXEC.BAT, CONFIG.SYS, COMMAND.COM and HIMEM.SYS with sizes and dates, framed in double-line box characters.</p>

        <div class="flex flex-wrap items-center gap-x-[2ch] min-h-[1.6em] px-[0.5ch] min-w-0 bg-twr06-ltgrey text-twr06-k text-[clamp(0.66rem,0.55rem+0.6vw,0.85rem)]">
          <span><span class="font-bold text-twr06-red">F1</span> Help</span>
          <span><span class="font-bold text-twr06-red">F3</span> Open</span>
          <span><span class="font-bold text-twr06-red">F10</span> Menu</span>
          <span class="ms-auto text-[#303030]">80 &times; 25</span>
        </div>
      </div>
    </div>

    <aside class="min-w-0 grid content-start gap-2.5 border border-twr06-line bg-twr06-surface p-[clamp(0.8rem,0.6rem+0.8vw,1.1rem)]">
      <h3 class="m-0 text-[0.72rem] tracking-[0.1em] uppercase text-twr06-ink2">Cell inspector &mdash; the grid the text lands on</h3>
      <div class="grid grid-cols-12 gap-px min-w-0 border border-twr06-line bg-twr06-line text-[clamp(0.8rem,0.6rem+1.4vw,1.35rem)]" aria-hidden="true">
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╔</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╦</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╗</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">█</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">▓</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">▒</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan text-twr06-yellow">░</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">A</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">B</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">C</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">D</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">║</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">─</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┼</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">─</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┤</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
        <span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╚</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╩</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">═</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">╝</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">└</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┴</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan">┘</span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span><span class="grid place-items-center aspect-square min-w-0 leading-none bg-twr06-blue text-twr06-ltcyan"> </span>
      </div>
      <p class="m-0 min-w-0 text-[0.8rem] leading-relaxed text-pretty text-twr06-ink2">Each box is <code class="bg-twr06-bg px-1">1ch</code> wide and one line-height tall. The frame is characters, not borders &mdash; a border would have to live between two cells, and there is no between.</p>
    </aside>

    <aside class="min-w-0 grid content-start gap-2.5 border border-twr06-line bg-twr06-surface p-[clamp(0.8rem,0.6rem+0.8vw,1.1rem)]">
      <h3 class="m-0 text-[0.72rem] tracking-[0.1em] uppercase text-twr06-ink2">CGA palette &mdash; 16 defined values</h3>
      <ul class="list-none m-0 p-0 min-w-0 grid gap-0.5 grid-cols-[repeat(auto-fit,minmax(min(100%,7.5rem),1fr))]">
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#000000]">0 black</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#0000aa]">1 blue</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#00aa00]">2 green</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#00aaaa]">3 cyan</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa0000]">4 red</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa00aa]">5 magenta</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aa5500]">6 brown</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#aaaaaa]">7 lt grey</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#555555]">8 dk grey</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#5555ff]">9 lt blue</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#55ff55]">10 lt green</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#55ffff]">11 lt cyan</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ff5555]">12 lt red</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ff55ff]">13 lt magenta</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ffff55]">14 yellow</li>
        <li class="min-w-0 flex items-center gap-2 px-2 py-1.5 text-[0.68rem] text-twr06-ink2 border border-twr06-line before:content-[''] before:flex-none before:size-4 before:border before:border-twr06-line before:bg-[#ffffff]">15 white</li>
      </ul>
    </aside>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-twr06-k: #000000;
  --color-twr06-blue: #0000aa;
  --color-twr06-cyan: #00aaaa;
  --color-twr06-ltgrey: #aaaaaa;
  --color-twr06-dkgrey: #555555;
  --color-twr06-ltcyan: #55ffff;
  --color-twr06-yellow: #ffff55;
  --color-twr06-white: #ffffff;
  --color-twr06-red: #aa0000;
  --color-twr06-bg: #c9ccd2;
  --color-twr06-bg: oklch(0.84 0.008 260);
  --color-twr06-surface: #eef0f3;
  --color-twr06-surface: oklch(0.95 0.004 260);
  --color-twr06-ink: #14171c;
  --color-twr06-ink: oklch(0.19 0.008 260);
  --color-twr06-ink2: #464e58;
  --color-twr06-ink2: oklch(0.42 0.014 260);
  --color-twr06-line: #9aa1ab;
  --color-twr06-line: oklch(0.71 0.012 260);
  --color-twr06-acc: #1247c9;
  --color-twr06-acc: oklch(0.45 0.2 264);
  --font-twr06-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
}
/* A feature query that substitutes the very property it tests cannot be a
   variant — the utility and the fallback would compete in the same layer. */

@supports not (font-size: 1cqi) {
  .twr-06__art {
    font-size: clamp(0.35rem, 1.6vw, 0.95rem);
  }
}

.twr-06:has(#twr-06-theme:checked) {
  --color-twr06-bg: #101318;
  --color-twr06-bg: oklch(0.18 0.008 260);
  --color-twr06-surface: #1a1e25;
  --color-twr06-surface: oklch(0.24 0.01 260);
  --color-twr06-ink: #eef1f5;
  --color-twr06-ink: oklch(0.96 0.004 260);
  --color-twr06-ink2: #a3abb6;
  --color-twr06-ink2: oklch(0.74 0.012 260);
  --color-twr06-line: #39414c;
  --color-twr06-line: oklch(0.35 0.014 260);
  --color-twr06-acc: #7fa8ff;
  --color-twr06-acc: oklch(0.76 0.13 264);
}
```

How this works

Text mode is not a font choice, it is an addressing model. A CGA or VGA text screen is a fixed array of cells — 80 across, 25 down — each holding one character code and one attribute byte. There is no position between cell 12 and cell 13. That is why DOS dialogs are built from box-drawing characters rather than lines: a line would have to live between cells, and there is no between.

ch is the unit that preserves the model, and it survives the port as an arbitrary value. One ch is the advance width of the zero glyph, which in a monospace face is every glyph's advance. Tailwind has no ch scale and should not: the design scale here is the character, not a spacing ramp. So the chrome measures itself in characters directly:

class="flex items-center min-h-[1.6em] px-[0.5ch] gap-x-[2ch]"

Fitting 80 columns is a font-size problem, and container queries solve it exactly. The field is 80 advances wide, so the only free variable is the advance. The screen gets @container and the <pre> gets text-[min(0.95rem,2.02cqi)] — 80 columns fill the container's inline size and stop. This is a case where the utility form is genuinely nicer than the CSS: the container and the query unit that depends on it are two classes on two elements you can see at once.

The responsive answer is honest, not clever. Below the md breakpoint the computed size drops under legibility, so a 40-column field takes over: hidden md:block on the wide art, md:hidden on the narrow. Scaling an 80-column screen onto a phone is period-accurate and unreadable, and the demo says so on screen.

The CGA palette is sixteen defined values, not a mood. Eight low-intensity and eight high-intensity colours as exact sRGB. Several authentic pairings fail modern contrast outright — dark grey #555555 on blue #0000aa is under 2:1 — so those stay decorative chrome while body text uses the light-cyan-on-blue pair that passes. The palette swatches use per-item arbitrary colours rather than tokens, because each hex appears exactly once and a token would add a name with no reuse.

What stays in CSS. The @supports not (font-size: 1cqi) fallback, because a feature query has no variant form and the fallback replaces the very property being tested; and the :has() surround flip. Everything else — the cell grid, the character-unit chrome, the palette, the responsive field swap — is in the markup.

Make it yours

  • Set the field width by editing the 2.02cqi coefficient: it is 160/80 minus frame padding, so a 40-column field wants roughly double.
  • Reskin the whole screen by swapping which palette tokens the screen and text use — bg-twr06-blue text-twr06-ltcyan to bg-twr06-k text-twr06-ltgrey is the DOS Shell look.
  • Swap the double-line frame characters for single-line ones by editing the <pre> content; the geometry is unchanged because both occupy one cell.
  • Raise the min() ceiling past 0.95rem for a large-display kiosk look; the field stays 80 columns and simply gets bigger.
  • Turn the inspector's grid lines off by removing the gap-px and its background — the text layer does not depend on them.
  • Add a second screen with a different palette pair by duplicating the frame; the tokens are shared but nothing is scoped to a single instance.

Gotchas — read before shipping

  • An 80-column field cannot fit a 320px viewport at a legible size. Drop to 40 columns at a breakpoint or accept a sub-8px cell — do not pretend scaling solves it.
  • Sizing chrome in px instead of ch or em puts the cell boundaries out of phase with the glyph advances, and the error accumulates into visible shear across a wide row.
  • cqi needs an ancestor with container-type: inline-size. Tailwind's @container class provides it, but if the query unit resolves against nothing the text silently falls back to its initial size.
  • The @supports fallback cannot be a variant here, because it substitutes the same property it tests. Keep it in CSS.
  • Not every monospace stack carries code page 437 box-drawing glyphs at the same advance. Test the frame in your fallback fonts or the frame shears where a glyph substitutes.
  • Authentic CGA pairings such as dark grey on blue fail SC 1.4.3 badly. Keep them for chrome, raise body text, and never carry meaning in the low-intensity pair alone.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Container query units (cqi) land at Chrome 105 / Safari 16 / Firefox 110, with a viewport-based font size as the @supports fallback. The ch unit has been Baseline since 2011 and the box-drawing characters are plain UTF-8, so the character grid itself works far further back. Tailwind v4's generated sheet is the binding floor at Chrome 111+, Safari 16.4+, Firefox 128+.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Arbitraryneeds bracket syntax

HTML & JSno Tailwind involved

Search CodeFronts

Loading…