06 / 20
All 20 in 20 CSS Retro UI
- 01CRT Scanlines and Screen Curvature
- 02Pixel Art Scaling with Image Rendering
- 03Dithering Patterns with CSS Gradients
- 04One Bit Bevel Borders with Box Shadow
- 05Classic Mac System 7 Window Chrome
- 06MS DOS Text Mode Interface Grid
- 07Teletext and Ceefax Block Graphics
- 08Amber and Green Phosphor Monitor
- 09Windows 95 Desktop Interface
- 10Retro Futuristic Terminal Interface
- 11Eight Bit Pixel Art Buttons and HUD
- 12Neo Brutalist Component Board
- 13Skeuomorphic Cassette Player
- 14Vaporwave Aesthetic Scene
- 15Retro Arcade Game Interface
- 16ASCII Art Terminal Interface
- 17Nineties Web Guestbook Widget
- 18Y2K Aqua Gloss Interface Kit
- 19Vintage Storefront Layout
- 20Retro Desktop Portfolio Template
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, laid out with CSS Grid and ch units, framed with code page 437 box-drawing characters and restricted to the 16-colour CGA palette as tokens. The constraint is the subject — nothing can sit between two cells, so every alignment decision is an integer.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="rt-06" aria-labelledby="rt-06-title">
<h2 class="rt-06__sr" id="rt-06-title">MS-DOS text mode character grid</h2>
<div class="rt-06__theme">
<input class="rt-06__theme-input" type="checkbox" id="rt-06-theme">
<label class="rt-06__theme-label" for="rt-06-theme">
<svg 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="rt-06__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-06__stage">
<p class="rt-06__badge">IBM CGA · 16 colours · 80 × 25 text mode · code page 437</p>
<div class="rt-06__frame">
<div class="rt-06__screen">
<div class="rt-06__menu" role="presentation">
<span class="rt-06__menu-hi">F</span><span>ile</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">E</span><span>dit</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">S</span><span>earch</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">O</span><span>ptions</span>
<span class="rt-06__menu-tail">MEM 640K</span>
</div>
<pre class="rt-06__art rt-06__art--wide" 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="rt-06__art rt-06__art--narrow" 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="rt-06__sr">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="rt-06__status">
<span><span class="rt-06__key">F1</span> Help</span>
<span><span class="rt-06__key">F3</span> Open</span>
<span><span class="rt-06__key">F10</span> Menu</span>
<span class="rt-06__status-tail">80 × 25</span>
</div>
</div>
</div>
<aside class="rt-06__inspect">
<h3 class="rt-06__inspect-h">Cell inspector — the grid the text lands on</h3>
<div class="rt-06__cells" aria-hidden="true">
<span class="rt-06__cell">╔</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╦</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╗</span><span class="rt-06__cell rt-06__cell--hi">█</span><span class="rt-06__cell rt-06__cell--hi">▓</span><span class="rt-06__cell rt-06__cell--hi">▒</span><span class="rt-06__cell rt-06__cell--hi">░</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">║</span><span class="rt-06__cell">A</span><span class="rt-06__cell">B</span><span class="rt-06__cell">║</span><span class="rt-06__cell">C</span><span class="rt-06__cell">D</span><span class="rt-06__cell">║</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┼</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┤</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">╚</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╩</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╝</span><span class="rt-06__cell">└</span><span class="rt-06__cell">┴</span><span class="rt-06__cell">┘</span><span class="rt-06__cell"> </span><span class="rt-06__cell"> </span>
</div>
<p class="rt-06__inspect-p">Each box is <code>1ch</code> wide and one line-height tall. The frame is characters, not borders — a border would have to live between two cells, and there is no between.</p>
</aside>
<aside class="rt-06__pal">
<h3 class="rt-06__inspect-h">CGA palette — 16 defined values</h3>
<ul class="rt-06__pal-list">
<li style="--rt-06-c: #000000">0 black</li>
<li style="--rt-06-c: #0000aa">1 blue</li>
<li style="--rt-06-c: #00aa00">2 green</li>
<li style="--rt-06-c: #00aaaa">3 cyan</li>
<li style="--rt-06-c: #aa0000">4 red</li>
<li style="--rt-06-c: #aa00aa">5 magenta</li>
<li style="--rt-06-c: #aa5500">6 brown</li>
<li style="--rt-06-c: #aaaaaa">7 lt grey</li>
<li style="--rt-06-c: #555555">8 dk grey</li>
<li style="--rt-06-c: #5555ff">9 lt blue</li>
<li style="--rt-06-c: #55ff55">10 lt green</li>
<li style="--rt-06-c: #55ffff">11 lt cyan</li>
<li style="--rt-06-c: #ff5555">12 lt red</li>
<li style="--rt-06-c: #ff55ff">13 lt magenta</li>
<li style="--rt-06-c: #ffff55">14 yellow</li>
<li style="--rt-06-c: #ffffff">15 white</li>
</ul>
</aside>
</div>
</section><section class="rt-06" aria-labelledby="rt-06-title">
<h2 class="rt-06__sr" id="rt-06-title">MS-DOS text mode character grid</h2>
<div class="rt-06__theme">
<input class="rt-06__theme-input" type="checkbox" id="rt-06-theme">
<label class="rt-06__theme-label" for="rt-06-theme">
<svg 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="rt-06__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-06__stage">
<p class="rt-06__badge">IBM CGA · 16 colours · 80 × 25 text mode · code page 437</p>
<div class="rt-06__frame">
<div class="rt-06__screen">
<div class="rt-06__menu" role="presentation">
<span class="rt-06__menu-hi">F</span><span>ile</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">E</span><span>dit</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">S</span><span>earch</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">O</span><span>ptions</span>
<span class="rt-06__menu-tail">MEM 640K</span>
</div>
<pre class="rt-06__art rt-06__art--wide" 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="rt-06__art rt-06__art--narrow" 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="rt-06__sr">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="rt-06__status">
<span><span class="rt-06__key">F1</span> Help</span>
<span><span class="rt-06__key">F3</span> Open</span>
<span><span class="rt-06__key">F10</span> Menu</span>
<span class="rt-06__status-tail">80 × 25</span>
</div>
</div>
</div>
<aside class="rt-06__inspect">
<h3 class="rt-06__inspect-h">Cell inspector — the grid the text lands on</h3>
<div class="rt-06__cells" aria-hidden="true">
<span class="rt-06__cell">╔</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╦</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╗</span><span class="rt-06__cell rt-06__cell--hi">█</span><span class="rt-06__cell rt-06__cell--hi">▓</span><span class="rt-06__cell rt-06__cell--hi">▒</span><span class="rt-06__cell rt-06__cell--hi">░</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">║</span><span class="rt-06__cell">A</span><span class="rt-06__cell">B</span><span class="rt-06__cell">║</span><span class="rt-06__cell">C</span><span class="rt-06__cell">D</span><span class="rt-06__cell">║</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┼</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┤</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">╚</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╩</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╝</span><span class="rt-06__cell">└</span><span class="rt-06__cell">┴</span><span class="rt-06__cell">┘</span><span class="rt-06__cell"> </span><span class="rt-06__cell"> </span>
</div>
<p class="rt-06__inspect-p">Each box is <code>1ch</code> wide and one line-height tall. The frame is characters, not borders — a border would have to live between two cells, and there is no between.</p>
</aside>
<aside class="rt-06__pal">
<h3 class="rt-06__inspect-h">CGA palette — 16 defined values</h3>
<ul class="rt-06__pal-list">
<li style="--rt-06-c: #000000">0 black</li>
<li style="--rt-06-c: #0000aa">1 blue</li>
<li style="--rt-06-c: #00aa00">2 green</li>
<li style="--rt-06-c: #00aaaa">3 cyan</li>
<li style="--rt-06-c: #aa0000">4 red</li>
<li style="--rt-06-c: #aa00aa">5 magenta</li>
<li style="--rt-06-c: #aa5500">6 brown</li>
<li style="--rt-06-c: #aaaaaa">7 lt grey</li>
<li style="--rt-06-c: #555555">8 dk grey</li>
<li style="--rt-06-c: #5555ff">9 lt blue</li>
<li style="--rt-06-c: #55ff55">10 lt green</li>
<li style="--rt-06-c: #55ffff">11 lt cyan</li>
<li style="--rt-06-c: #ff5555">12 lt red</li>
<li style="--rt-06-c: #ff55ff">13 lt magenta</li>
<li style="--rt-06-c: #ffff55">14 yellow</li>
<li style="--rt-06-c: #ffffff">15 white</li>
</ul>
</aside>
</div>
</section>.rt-06 {
--rt-06-cols: 80;
--rt-06-k: #000000;
--rt-06-blue: #0000aa;
--rt-06-cyan: #00aaaa;
--rt-06-ltgrey: #aaaaaa;
--rt-06-dkgrey: #555555;
--rt-06-ltcyan: #55ffff;
--rt-06-yellow: #ffff55;
--rt-06-white: #ffffff;
--rt-06-red: #aa0000;
--rt-06-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-06-bg);
color: var(--rt-06-ink);
font-family: var(--rt-06-mono);
-webkit-text-size-adjust: 100%;
}
.rt-06 *,
.rt-06 *::before,
.rt-06 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-06 {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
.rt-06[data-theme="dark"],
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
@media (prefers-color-scheme: light) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
@media (prefers-color-scheme: dark) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
}
}
.rt-06__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-06__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-06__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-06__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-06-line);
background: var(--rt-06-surface);
color: var(--rt-06-ink-2);
cursor: pointer;
}
.rt-06__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-06__theme-input:focus-visible + .rt-06__theme-label {
outline: 2px solid var(--rt-06-acc);
outline-offset: 2px;
}
.rt-06__stage {
max-inline-size: 72rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.6rem, 0.4rem + 1.5vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-06__badge {
margin: 0;
justify-self: start;
min-inline-size: 0;
font-size: 0.68rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
padding: 0.35rem 0.7rem;
background: var(--rt-06-surface);
}
.rt-06__frame {
min-inline-size: 0;
background: #6d6f70;
padding: clamp(0.5rem, 0.3rem + 1vw, 1rem);
border-radius: 6px;
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);
}
.rt-06__screen {
container-type: inline-size;
min-inline-size: 0;
overflow: hidden;
background: var(--rt-06-blue);
color: var(--rt-06-ltgrey);
padding: 0;
border: 2px solid #1b1d1f;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-content: start;
}
.rt-06__menu {
display: flex;
align-items: center;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
overflow: hidden;
white-space: nowrap;
}
/* CGA dark red (index 4) rather than bright red (index 12): 4.71:1 on the
light-grey menu bar where bright red is only 3.34:1, and both are real
entries in the same 16-colour palette. */
.rt-06__menu-hi {
color: #800000;
font-weight: 700;
}
.rt-06__menu-gap {
inline-size: 2ch;
flex: none;
}
.rt-06__menu-tail {
margin-inline-start: auto;
color: #303030;
padding-inline-start: 1ch;
}
.rt-06__art {
margin: 0;
padding: 0.5em 0;
font-family: inherit;
white-space: pre;
color: var(--rt-06-ltcyan);
min-inline-size: 0;
overflow: hidden;
font-size: min(0.95rem, 2.02cqi);
line-height: 1.16;
text-align: center;
}
@supports not (font-size: 1cqi) {
.rt-06__art {
font-size: clamp(0.35rem, 1.6vw, 0.95rem);
}
}
.rt-06__art--narrow {
display: block;
}
.rt-06__art--wide {
display: none;
}
@media (min-width: 52rem) {
.rt-06__art--narrow {
display: none;
}
.rt-06__art--wide {
display: block;
}
}
.rt-06__status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0 2ch;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
}
.rt-06__key {
color: #800000;
font-weight: 700;
}
.rt-06__status-tail {
margin-inline-start: auto;
color: #303030;
}
.rt-06__inspect,
.rt-06__pal {
min-inline-size: 0;
background: var(--rt-06-surface);
border: 1px solid var(--rt-06-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
display: grid;
gap: 0.7rem;
align-content: start;
}
.rt-06__inspect-h {
margin: 0;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
}
.rt-06__cells {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1px;
background: var(--rt-06-line);
border: 1px solid var(--rt-06-line);
min-inline-size: 0;
font-size: clamp(0.8rem, 0.6rem + 1.4vw, 1.35rem);
}
.rt-06__cell {
display: grid;
place-items: center;
aspect-ratio: 1 / 1;
min-inline-size: 0;
background: var(--rt-06-blue);
color: var(--rt-06-ltcyan);
line-height: 1;
}
.rt-06__cell--hi {
color: var(--rt-06-yellow);
}
.rt-06__inspect-p {
margin: 0;
font-size: 0.8rem;
line-height: 1.55;
color: var(--rt-06-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-06__inspect-p code {
background: var(--rt-06-bg);
padding: 0.05em 0.3em;
}
.rt-06__pal-list {
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
gap: 2px;
}
.rt-06__pal-list li {
min-inline-size: 0;
font-size: 0.68rem;
padding: 0.4rem 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
}
.rt-06__pal-list li::before {
content: "";
inline-size: 16px;
block-size: 16px;
flex: none;
background: var(--rt-06-c);
border: 1px solid var(--rt-06-line);
}
@media (min-width: 56rem) {
.rt-06__stage {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.rt-06__badge,
.rt-06__frame {
grid-column: 1 / -1;
}
}.rt-06 {
--rt-06-cols: 80;
--rt-06-k: #000000;
--rt-06-blue: #0000aa;
--rt-06-cyan: #00aaaa;
--rt-06-ltgrey: #aaaaaa;
--rt-06-dkgrey: #555555;
--rt-06-ltcyan: #55ffff;
--rt-06-yellow: #ffff55;
--rt-06-white: #ffffff;
--rt-06-red: #aa0000;
--rt-06-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-06-bg);
color: var(--rt-06-ink);
font-family: var(--rt-06-mono);
-webkit-text-size-adjust: 100%;
}
.rt-06 *,
.rt-06 *::before,
.rt-06 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-06 {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
.rt-06[data-theme="dark"],
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
@media (prefers-color-scheme: light) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
@media (prefers-color-scheme: dark) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
}
}
.rt-06__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-06__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-06__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-06__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-06-line);
background: var(--rt-06-surface);
color: var(--rt-06-ink-2);
cursor: pointer;
}
.rt-06__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-06__theme-input:focus-visible + .rt-06__theme-label {
outline: 2px solid var(--rt-06-acc);
outline-offset: 2px;
}
.rt-06__stage {
max-inline-size: 72rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.6rem, 0.4rem + 1.5vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-06__badge {
margin: 0;
justify-self: start;
min-inline-size: 0;
font-size: 0.68rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
padding: 0.35rem 0.7rem;
background: var(--rt-06-surface);
}
.rt-06__frame {
min-inline-size: 0;
background: #6d6f70;
padding: clamp(0.5rem, 0.3rem + 1vw, 1rem);
border-radius: 6px;
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);
}
.rt-06__screen {
container-type: inline-size;
min-inline-size: 0;
overflow: hidden;
background: var(--rt-06-blue);
color: var(--rt-06-ltgrey);
padding: 0;
border: 2px solid #1b1d1f;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-content: start;
}
.rt-06__menu {
display: flex;
align-items: center;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
overflow: hidden;
white-space: nowrap;
}
/* CGA dark red (index 4) rather than bright red (index 12): 4.71:1 on the
light-grey menu bar where bright red is only 3.34:1, and both are real
entries in the same 16-colour palette. */
.rt-06__menu-hi {
color: #800000;
font-weight: 700;
}
.rt-06__menu-gap {
inline-size: 2ch;
flex: none;
}
.rt-06__menu-tail {
margin-inline-start: auto;
color: #303030;
padding-inline-start: 1ch;
}
.rt-06__art {
margin: 0;
padding: 0.5em 0;
font-family: inherit;
white-space: pre;
color: var(--rt-06-ltcyan);
min-inline-size: 0;
overflow: hidden;
font-size: min(0.95rem, 2.02cqi);
line-height: 1.16;
text-align: center;
}
@supports not (font-size: 1cqi) {
.rt-06__art {
font-size: clamp(0.35rem, 1.6vw, 0.95rem);
}
}
.rt-06__art--narrow {
display: block;
}
.rt-06__art--wide {
display: none;
}
@media (min-width: 52rem) {
.rt-06__art--narrow {
display: none;
}
.rt-06__art--wide {
display: block;
}
}
.rt-06__status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0 2ch;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
}
.rt-06__key {
color: #800000;
font-weight: 700;
}
.rt-06__status-tail {
margin-inline-start: auto;
color: #303030;
}
.rt-06__inspect,
.rt-06__pal {
min-inline-size: 0;
background: var(--rt-06-surface);
border: 1px solid var(--rt-06-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
display: grid;
gap: 0.7rem;
align-content: start;
}
.rt-06__inspect-h {
margin: 0;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
}
.rt-06__cells {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1px;
background: var(--rt-06-line);
border: 1px solid var(--rt-06-line);
min-inline-size: 0;
font-size: clamp(0.8rem, 0.6rem + 1.4vw, 1.35rem);
}
.rt-06__cell {
display: grid;
place-items: center;
aspect-ratio: 1 / 1;
min-inline-size: 0;
background: var(--rt-06-blue);
color: var(--rt-06-ltcyan);
line-height: 1;
}
.rt-06__cell--hi {
color: var(--rt-06-yellow);
}
.rt-06__inspect-p {
margin: 0;
font-size: 0.8rem;
line-height: 1.55;
color: var(--rt-06-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-06__inspect-p code {
background: var(--rt-06-bg);
padding: 0.05em 0.3em;
}
.rt-06__pal-list {
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
gap: 2px;
}
.rt-06__pal-list li {
min-inline-size: 0;
font-size: 0.68rem;
padding: 0.4rem 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
}
.rt-06__pal-list li::before {
content: "";
inline-size: 16px;
block-size: 16px;
flex: none;
background: var(--rt-06-c);
border: 1px solid var(--rt-06-line);
}
@media (min-width: 56rem) {
.rt-06__stage {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.rt-06__badge,
.rt-06__frame {
grid-column: 1 / -1;
}
}Here's a working CSS Retro UI 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: MS DOS Text Mode Interface Grid
Source: https://codefronts.com/design-styles/css-retro-designs/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, laid out with CSS Grid and ch units, framed with code page 437 box-drawing characters and restricted to the 16-colour CGA palette as tokens. The constraint is the subject — nothing can sit between two cells, so every alignment decision is an integer.
## HTML
```html
<section class="rt-06" aria-labelledby="rt-06-title">
<h2 class="rt-06__sr" id="rt-06-title">MS-DOS text mode character grid</h2>
<div class="rt-06__theme">
<input class="rt-06__theme-input" type="checkbox" id="rt-06-theme">
<label class="rt-06__theme-label" for="rt-06-theme">
<svg 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="rt-06__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-06__stage">
<p class="rt-06__badge">IBM CGA · 16 colours · 80 × 25 text mode · code page 437</p>
<div class="rt-06__frame">
<div class="rt-06__screen">
<div class="rt-06__menu" role="presentation">
<span class="rt-06__menu-hi">F</span><span>ile</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">E</span><span>dit</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">S</span><span>earch</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">O</span><span>ptions</span>
<span class="rt-06__menu-tail">MEM 640K</span>
</div>
<pre class="rt-06__art rt-06__art--wide" 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="rt-06__art rt-06__art--narrow" 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="rt-06__sr">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="rt-06__status">
<span><span class="rt-06__key">F1</span> Help</span>
<span><span class="rt-06__key">F3</span> Open</span>
<span><span class="rt-06__key">F10</span> Menu</span>
<span class="rt-06__status-tail">80 × 25</span>
</div>
</div>
</div>
<aside class="rt-06__inspect">
<h3 class="rt-06__inspect-h">Cell inspector — the grid the text lands on</h3>
<div class="rt-06__cells" aria-hidden="true">
<span class="rt-06__cell">╔</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╦</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╗</span><span class="rt-06__cell rt-06__cell--hi">█</span><span class="rt-06__cell rt-06__cell--hi">▓</span><span class="rt-06__cell rt-06__cell--hi">▒</span><span class="rt-06__cell rt-06__cell--hi">░</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">║</span><span class="rt-06__cell">A</span><span class="rt-06__cell">B</span><span class="rt-06__cell">║</span><span class="rt-06__cell">C</span><span class="rt-06__cell">D</span><span class="rt-06__cell">║</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┼</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┤</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">╚</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╩</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╝</span><span class="rt-06__cell">└</span><span class="rt-06__cell">┴</span><span class="rt-06__cell">┘</span><span class="rt-06__cell"> </span><span class="rt-06__cell"> </span>
</div>
<p class="rt-06__inspect-p">Each box is <code>1ch</code> wide and one line-height tall. The frame is characters, not borders — a border would have to live between two cells, and there is no between.</p>
</aside>
<aside class="rt-06__pal">
<h3 class="rt-06__inspect-h">CGA palette — 16 defined values</h3>
<ul class="rt-06__pal-list">
<li style="--rt-06-c: #000000">0 black</li>
<li style="--rt-06-c: #0000aa">1 blue</li>
<li style="--rt-06-c: #00aa00">2 green</li>
<li style="--rt-06-c: #00aaaa">3 cyan</li>
<li style="--rt-06-c: #aa0000">4 red</li>
<li style="--rt-06-c: #aa00aa">5 magenta</li>
<li style="--rt-06-c: #aa5500">6 brown</li>
<li style="--rt-06-c: #aaaaaa">7 lt grey</li>
<li style="--rt-06-c: #555555">8 dk grey</li>
<li style="--rt-06-c: #5555ff">9 lt blue</li>
<li style="--rt-06-c: #55ff55">10 lt green</li>
<li style="--rt-06-c: #55ffff">11 lt cyan</li>
<li style="--rt-06-c: #ff5555">12 lt red</li>
<li style="--rt-06-c: #ff55ff">13 lt magenta</li>
<li style="--rt-06-c: #ffff55">14 yellow</li>
<li style="--rt-06-c: #ffffff">15 white</li>
</ul>
</aside>
</div>
</section>
```
## CSS
```css
.rt-06 {
--rt-06-cols: 80;
--rt-06-k: #000000;
--rt-06-blue: #0000aa;
--rt-06-cyan: #00aaaa;
--rt-06-ltgrey: #aaaaaa;
--rt-06-dkgrey: #555555;
--rt-06-ltcyan: #55ffff;
--rt-06-yellow: #ffff55;
--rt-06-white: #ffffff;
--rt-06-red: #aa0000;
--rt-06-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-06-bg);
color: var(--rt-06-ink);
font-family: var(--rt-06-mono);
-webkit-text-size-adjust: 100%;
}
.rt-06 *,
.rt-06 *::before,
.rt-06 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-06 {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
.rt-06[data-theme="dark"],
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
@media (prefers-color-scheme: light) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
@media (prefers-color-scheme: dark) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
}
}
.rt-06__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-06__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-06__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-06__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-06-line);
background: var(--rt-06-surface);
color: var(--rt-06-ink-2);
cursor: pointer;
}
.rt-06__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-06__theme-input:focus-visible + .rt-06__theme-label {
outline: 2px solid var(--rt-06-acc);
outline-offset: 2px;
}
.rt-06__stage {
max-inline-size: 72rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.6rem, 0.4rem + 1.5vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-06__badge {
margin: 0;
justify-self: start;
min-inline-size: 0;
font-size: 0.68rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
padding: 0.35rem 0.7rem;
background: var(--rt-06-surface);
}
.rt-06__frame {
min-inline-size: 0;
background: #6d6f70;
padding: clamp(0.5rem, 0.3rem + 1vw, 1rem);
border-radius: 6px;
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);
}
.rt-06__screen {
container-type: inline-size;
min-inline-size: 0;
overflow: hidden;
background: var(--rt-06-blue);
color: var(--rt-06-ltgrey);
padding: 0;
border: 2px solid #1b1d1f;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-content: start;
}
.rt-06__menu {
display: flex;
align-items: center;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
overflow: hidden;
white-space: nowrap;
}
/* CGA dark red (index 4) rather than bright red (index 12): 4.71:1 on the
light-grey menu bar where bright red is only 3.34:1, and both are real
entries in the same 16-colour palette. */
.rt-06__menu-hi {
color: #800000;
font-weight: 700;
}
.rt-06__menu-gap {
inline-size: 2ch;
flex: none;
}
.rt-06__menu-tail {
margin-inline-start: auto;
color: #303030;
padding-inline-start: 1ch;
}
.rt-06__art {
margin: 0;
padding: 0.5em 0;
font-family: inherit;
white-space: pre;
color: var(--rt-06-ltcyan);
min-inline-size: 0;
overflow: hidden;
font-size: min(0.95rem, 2.02cqi);
line-height: 1.16;
text-align: center;
}
@supports not (font-size: 1cqi) {
.rt-06__art {
font-size: clamp(0.35rem, 1.6vw, 0.95rem);
}
}
.rt-06__art--narrow {
display: block;
}
.rt-06__art--wide {
display: none;
}
@media (min-width: 52rem) {
.rt-06__art--narrow {
display: none;
}
.rt-06__art--wide {
display: block;
}
}
.rt-06__status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0 2ch;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
}
.rt-06__key {
color: #800000;
font-weight: 700;
}
.rt-06__status-tail {
margin-inline-start: auto;
color: #303030;
}
.rt-06__inspect,
.rt-06__pal {
min-inline-size: 0;
background: var(--rt-06-surface);
border: 1px solid var(--rt-06-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
display: grid;
gap: 0.7rem;
align-content: start;
}
.rt-06__inspect-h {
margin: 0;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
}
.rt-06__cells {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1px;
background: var(--rt-06-line);
border: 1px solid var(--rt-06-line);
min-inline-size: 0;
font-size: clamp(0.8rem, 0.6rem + 1.4vw, 1.35rem);
}
.rt-06__cell {
display: grid;
place-items: center;
aspect-ratio: 1 / 1;
min-inline-size: 0;
background: var(--rt-06-blue);
color: var(--rt-06-ltcyan);
line-height: 1;
}
.rt-06__cell--hi {
color: var(--rt-06-yellow);
}
.rt-06__inspect-p {
margin: 0;
font-size: 0.8rem;
line-height: 1.55;
color: var(--rt-06-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-06__inspect-p code {
background: var(--rt-06-bg);
padding: 0.05em 0.3em;
}
.rt-06__pal-list {
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
gap: 2px;
}
.rt-06__pal-list li {
min-inline-size: 0;
font-size: 0.68rem;
padding: 0.4rem 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
}
.rt-06__pal-list li::before {
content: "";
inline-size: 16px;
block-size: 16px;
flex: none;
background: var(--rt-06-c);
border: 1px solid var(--rt-06-line);
}
@media (min-width: 56rem) {
.rt-06__stage {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.rt-06__badge,
.rt-06__frame {
grid-column: 1 / -1;
}
}
```Here's a working CSS Retro UI 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: MS DOS Text Mode Interface Grid
Source: https://codefronts.com/design-styles/css-retro-designs/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, laid out with CSS Grid and ch units, framed with code page 437 box-drawing characters and restricted to the 16-colour CGA palette as tokens. The constraint is the subject — nothing can sit between two cells, so every alignment decision is an integer.
## HTML
```html
<section class="rt-06" aria-labelledby="rt-06-title">
<h2 class="rt-06__sr" id="rt-06-title">MS-DOS text mode character grid</h2>
<div class="rt-06__theme">
<input class="rt-06__theme-input" type="checkbox" id="rt-06-theme">
<label class="rt-06__theme-label" for="rt-06-theme">
<svg 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="rt-06__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-06__stage">
<p class="rt-06__badge">IBM CGA · 16 colours · 80 × 25 text mode · code page 437</p>
<div class="rt-06__frame">
<div class="rt-06__screen">
<div class="rt-06__menu" role="presentation">
<span class="rt-06__menu-hi">F</span><span>ile</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">E</span><span>dit</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">S</span><span>earch</span>
<span class="rt-06__menu-gap"></span>
<span class="rt-06__menu-hi">O</span><span>ptions</span>
<span class="rt-06__menu-tail">MEM 640K</span>
</div>
<pre class="rt-06__art rt-06__art--wide" 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="rt-06__art rt-06__art--narrow" 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="rt-06__sr">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="rt-06__status">
<span><span class="rt-06__key">F1</span> Help</span>
<span><span class="rt-06__key">F3</span> Open</span>
<span><span class="rt-06__key">F10</span> Menu</span>
<span class="rt-06__status-tail">80 × 25</span>
</div>
</div>
</div>
<aside class="rt-06__inspect">
<h3 class="rt-06__inspect-h">Cell inspector — the grid the text lands on</h3>
<div class="rt-06__cells" aria-hidden="true">
<span class="rt-06__cell">╔</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╦</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╗</span><span class="rt-06__cell rt-06__cell--hi">█</span><span class="rt-06__cell rt-06__cell--hi">▓</span><span class="rt-06__cell rt-06__cell--hi">▒</span><span class="rt-06__cell rt-06__cell--hi">░</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">║</span><span class="rt-06__cell">A</span><span class="rt-06__cell">B</span><span class="rt-06__cell">║</span><span class="rt-06__cell">C</span><span class="rt-06__cell">D</span><span class="rt-06__cell">║</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┼</span><span class="rt-06__cell">─</span><span class="rt-06__cell">┤</span><span class="rt-06__cell"> </span>
<span class="rt-06__cell">╚</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╩</span><span class="rt-06__cell">═</span><span class="rt-06__cell">═</span><span class="rt-06__cell">╝</span><span class="rt-06__cell">└</span><span class="rt-06__cell">┴</span><span class="rt-06__cell">┘</span><span class="rt-06__cell"> </span><span class="rt-06__cell"> </span>
</div>
<p class="rt-06__inspect-p">Each box is <code>1ch</code> wide and one line-height tall. The frame is characters, not borders — a border would have to live between two cells, and there is no between.</p>
</aside>
<aside class="rt-06__pal">
<h3 class="rt-06__inspect-h">CGA palette — 16 defined values</h3>
<ul class="rt-06__pal-list">
<li style="--rt-06-c: #000000">0 black</li>
<li style="--rt-06-c: #0000aa">1 blue</li>
<li style="--rt-06-c: #00aa00">2 green</li>
<li style="--rt-06-c: #00aaaa">3 cyan</li>
<li style="--rt-06-c: #aa0000">4 red</li>
<li style="--rt-06-c: #aa00aa">5 magenta</li>
<li style="--rt-06-c: #aa5500">6 brown</li>
<li style="--rt-06-c: #aaaaaa">7 lt grey</li>
<li style="--rt-06-c: #555555">8 dk grey</li>
<li style="--rt-06-c: #5555ff">9 lt blue</li>
<li style="--rt-06-c: #55ff55">10 lt green</li>
<li style="--rt-06-c: #55ffff">11 lt cyan</li>
<li style="--rt-06-c: #ff5555">12 lt red</li>
<li style="--rt-06-c: #ff55ff">13 lt magenta</li>
<li style="--rt-06-c: #ffff55">14 yellow</li>
<li style="--rt-06-c: #ffffff">15 white</li>
</ul>
</aside>
</div>
</section>
```
## CSS
```css
.rt-06 {
--rt-06-cols: 80;
--rt-06-k: #000000;
--rt-06-blue: #0000aa;
--rt-06-cyan: #00aaaa;
--rt-06-ltgrey: #aaaaaa;
--rt-06-dkgrey: #555555;
--rt-06-ltcyan: #55ffff;
--rt-06-yellow: #ffff55;
--rt-06-white: #ffffff;
--rt-06-red: #aa0000;
--rt-06-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-06-bg);
color: var(--rt-06-ink);
font-family: var(--rt-06-mono);
-webkit-text-size-adjust: 100%;
}
.rt-06 *,
.rt-06 *::before,
.rt-06 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-06 {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
.rt-06[data-theme="dark"],
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
@media (prefers-color-scheme: light) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #101318;
--rt-06-bg: oklch(18% 0.008 260);
--rt-06-surface: #1a1e25;
--rt-06-surface: oklch(24% 0.01 260);
--rt-06-ink: #eef1f5;
--rt-06-ink: oklch(96% 0.004 260);
--rt-06-ink-2: #a3abb6;
--rt-06-ink-2: oklch(74% 0.012 260);
--rt-06-line: #39414c;
--rt-06-line: oklch(35% 0.014 260);
--rt-06-acc: #7fa8ff;
--rt-06-acc: oklch(76% 0.13 264);
}
}
@media (prefers-color-scheme: dark) {
.rt-06:has(.rt-06__theme-input:checked) {
--rt-06-bg: #c9ccd2;
--rt-06-bg: oklch(84% 0.008 260);
--rt-06-surface: #eef0f3;
--rt-06-surface: oklch(95% 0.004 260);
--rt-06-ink: #14171c;
--rt-06-ink: oklch(19% 0.008 260);
--rt-06-ink-2: #464e58;
--rt-06-ink-2: oklch(42% 0.014 260);
--rt-06-line: #9aa1ab;
--rt-06-line: oklch(71% 0.012 260);
--rt-06-acc: #1247c9;
--rt-06-acc: oklch(45% 0.2 264);
}
}
.rt-06__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-06__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-06__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-06__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-06-line);
background: var(--rt-06-surface);
color: var(--rt-06-ink-2);
cursor: pointer;
}
.rt-06__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-06__theme-input:focus-visible + .rt-06__theme-label {
outline: 2px solid var(--rt-06-acc);
outline-offset: 2px;
}
.rt-06__stage {
max-inline-size: 72rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.6rem, 0.4rem + 1.5vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-06__badge {
margin: 0;
justify-self: start;
min-inline-size: 0;
font-size: 0.68rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
padding: 0.35rem 0.7rem;
background: var(--rt-06-surface);
}
.rt-06__frame {
min-inline-size: 0;
background: #6d6f70;
padding: clamp(0.5rem, 0.3rem + 1vw, 1rem);
border-radius: 6px;
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);
}
.rt-06__screen {
container-type: inline-size;
min-inline-size: 0;
overflow: hidden;
background: var(--rt-06-blue);
color: var(--rt-06-ltgrey);
padding: 0;
border: 2px solid #1b1d1f;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-content: start;
}
.rt-06__menu {
display: flex;
align-items: center;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
overflow: hidden;
white-space: nowrap;
}
/* CGA dark red (index 4) rather than bright red (index 12): 4.71:1 on the
light-grey menu bar where bright red is only 3.34:1, and both are real
entries in the same 16-colour palette. */
.rt-06__menu-hi {
color: #800000;
font-weight: 700;
}
.rt-06__menu-gap {
inline-size: 2ch;
flex: none;
}
.rt-06__menu-tail {
margin-inline-start: auto;
color: #303030;
padding-inline-start: 1ch;
}
.rt-06__art {
margin: 0;
padding: 0.5em 0;
font-family: inherit;
white-space: pre;
color: var(--rt-06-ltcyan);
min-inline-size: 0;
overflow: hidden;
font-size: min(0.95rem, 2.02cqi);
line-height: 1.16;
text-align: center;
}
@supports not (font-size: 1cqi) {
.rt-06__art {
font-size: clamp(0.35rem, 1.6vw, 0.95rem);
}
}
.rt-06__art--narrow {
display: block;
}
.rt-06__art--wide {
display: none;
}
@media (min-width: 52rem) {
.rt-06__art--narrow {
display: none;
}
.rt-06__art--wide {
display: block;
}
}
.rt-06__status {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0 2ch;
min-block-size: 1.6em;
padding-inline: 0.5ch;
background: var(--rt-06-ltgrey);
color: var(--rt-06-k);
font-size: clamp(0.66rem, 0.55rem + 0.6vw, 0.85rem);
min-inline-size: 0;
}
.rt-06__key {
color: #800000;
font-weight: 700;
}
.rt-06__status-tail {
margin-inline-start: auto;
color: #303030;
}
.rt-06__inspect,
.rt-06__pal {
min-inline-size: 0;
background: var(--rt-06-surface);
border: 1px solid var(--rt-06-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
display: grid;
gap: 0.7rem;
align-content: start;
}
.rt-06__inspect-h {
margin: 0;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-06-ink-2);
}
.rt-06__cells {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1px;
background: var(--rt-06-line);
border: 1px solid var(--rt-06-line);
min-inline-size: 0;
font-size: clamp(0.8rem, 0.6rem + 1.4vw, 1.35rem);
}
.rt-06__cell {
display: grid;
place-items: center;
aspect-ratio: 1 / 1;
min-inline-size: 0;
background: var(--rt-06-blue);
color: var(--rt-06-ltcyan);
line-height: 1;
}
.rt-06__cell--hi {
color: var(--rt-06-yellow);
}
.rt-06__inspect-p {
margin: 0;
font-size: 0.8rem;
line-height: 1.55;
color: var(--rt-06-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-06__inspect-p code {
background: var(--rt-06-bg);
padding: 0.05em 0.3em;
}
.rt-06__pal-list {
list-style: none;
margin: 0;
padding: 0;
min-inline-size: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
gap: 2px;
}
.rt-06__pal-list li {
min-inline-size: 0;
font-size: 0.68rem;
padding: 0.4rem 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--rt-06-ink-2);
border: 1px solid var(--rt-06-line);
}
.rt-06__pal-list li::before {
content: "";
inline-size: 16px;
block-size: 16px;
flex: none;
background: var(--rt-06-c);
border: 1px solid var(--rt-06-line);
}
@media (min-width: 56rem) {
.rt-06__stage {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.rt-06__badge,
.rt-06__frame {
grid-column: 1 / -1;
}
}
```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 — and each cell holds one character code plus one attribute byte for foreground and background. There is no position between cell 12 and cell 13. Every interface built on that hardware inherited the constraint, which is why DOS-era dialogs are built from box-drawing characters instead of lines: a line would need to live between cells, and there is no between.
ch is the unit that preserves the model. One ch is the advance width of the "0" glyph, which in a monospace face is the advance width of every glyph. Size the grid columns at 1ch and the layout can only ever land on cell boundaries. Use px or rem instead and the grid drifts out of phase with the text by a fraction of a character per column, which compounds into a visible shear about forty columns in.
Fitting 80 columns is a font-size problem, not a layout problem. The field width is fixed at 80 character advances, so the only free variable is the size of the advance. A container query unit solves this exactly: font-size: min(1rem, 2.05cqi) makes 80 columns fill the frame's inline size and no more. Below about 52rem the resulting size drops under legibility, so this demo switches to a 40-column field — the honest answer, because scaling an 80-column screen onto a phone produces something that is period-accurate and unreadable.
The CGA palette is sixteen defined values, not a mood. Eight low-intensity colours and eight high-intensity ones, specified as exact sRGB. Several authentic pairings fail modern contrast outright — the classic dark grey #555555 on blue #0000AA is under 2:1 — so this demo keeps those pairings for decorative chrome only and raises every body-text pair to a passing combination, which in practice means the light greys and yellow on the blue field.
Make it yours
- Set
--rt-06-cols: 40to force the narrow field at any width; every measurement follows from that one number. - Change
--rt-06-fgand--rt-06-bgto any two CGA entries to reskin the whole screen; the palette tokens are named for their original attribute values. - Swap the double-line frame characters for single-line ones by editing the frame rows — the geometry is unchanged because both occupy one cell.
- Raise the maximum font size past
1remfor a large-display kiosk look; the field stays 80 columns and simply gets bigger. - Recolour the menu bar to the black-on-light-grey used by DOS Shell rather than the Turbo Vision blue by swapping two tokens.
- Turn the cell inspector's grid lines off by removing its background gradient — the text layer does not depend on them.
Gotchas — read before shipping
- An 80-column character field cannot fit a 320px viewport at a legible size. Either drop to 40 columns at a breakpoint or accept a sub-8px cell; this demo drops and says so on screen.
- Sizing the grid in
pxinstead ofchputs the cell boundaries out of phase with the glyph advances, and the error accumulates into visible shear across a wide row. - Not every monospace stack has code page 437 box-drawing glyphs at the same advance width. Test the frame characters in your fallback fonts, or the frame breaks alignment where the 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
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
container query units (cqi) set a Chrome 105 / Safari 16 / Firefox 110 floor for the auto-fitting field, with a @media-based font size as the fallback for engines without them. oklch() and the :has() theme toggle are still the higher floor at Firefox 121. The ch unit itself is Baseline since 2011 and the box-drawing characters are plain UTF-8, so the character grid works far further back.