Tailwind v4MIT licensed

Tailwind Nineties Web Guestbook Widget

A personal-homepage guestbook: odometer hit counter, webring navigation, signed entries and a “best viewed in” badge, on the grey ground and ridged borders of the era. The point it makes is the one that matters most about this period — reproduce the look, never the markup, because everything that made these pages work is now the reason they fail.

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-17 relative block w-full min-h-svh box-border bg-twr17-face text-twr17-ink font-twr17-sans [background-image:repeating-conic-gradient(var(--color-twr17-tileb)_0%_25%,var(--color-twr17-face)_0%_50%)] [background-size:8px_8px]" aria-labelledby="twr-17-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-17-title">Nineties personal homepage guestbook</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-17-theme">
    <label class="grid place-items-center size-11 cursor-pointer bg-twr17-face2 text-twr17-ink2 twr17-ridge peer-focus-visible:outline-2 peer-focus-visible:outline-twr17-acc peer-focus-visible:outline-offset-2" for="twr-17-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 ground</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-5 max-w-[64rem] mx-auto px-[clamp(0.75rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
    <header class="min-w-0 grid justify-items-center gap-1.5 text-center bg-twr17-face2 px-4 py-5 twr17-ridge">
      <h3 class="m-0 min-w-0 font-twr17-serif font-bold text-[clamp(1.5rem,1.1rem+2.4vw,2.6rem)] text-twr17-acc [text-shadow:1px_1px_0_var(--color-twr17-hi)]">Dave&rsquo;s Homepage</h3>
      <p class="m-0 font-twr17-mono text-[0.75rem] text-twr17-hot">~ under construction since 1997 ~</p>
    </header>

    <div class="grid grid-cols-[minmax(0,1fr)] gap-5 lg:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)] lg:items-start">
      <article class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge" aria-labelledby="twr-17-gb">
        <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc" id="twr-17-gb">Sign my guestbook</h4>
        <ol class="list-none m-0 p-0 grid gap-3 min-w-0">
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">AC</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Alex Chen</b> &middot; 14 Nov 1997 &middot; Manchester</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Great page! Love the MIDI. Come see my Star Trek shrine sometime.</p>
            </div>
          </li>
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">PS</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Priya Sharma</b> &middot; 2 Dec 1997 &middot; Leeds</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Added you to the ring. The tiled background is a bold choice and I respect it.</p>
            </div>
          </li>
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">JL</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Jordan Lee</b> &middot; 19 Jan 1998 &middot; Bristol</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Your links page is the best on the web. Signed, a fellow traveller.</p>
            </div>
          </li>
        </ol>
        <p class="m-0 min-w-0 text-[0.75rem] leading-relaxed text-pretty text-twr17-ink2">Entries are a real ordered list. No table, no spacer image, no <code class="font-twr17-mono bg-twr17-face px-1">&lt;font&gt;</code> anywhere in this widget.</p>
      </article>

      <aside class="min-w-0 grid content-start gap-5">
        <div class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc">Visitors</h4>
          <div class="flex gap-1 min-w-0" role="img" aria-label="Visitor count: 0 0 4 2 1 8 7">
            <span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">0</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">0</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">4</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">2</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">1</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">8</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">7</span>
          </div>
          <p class="m-0 text-[0.72rem] text-twr17-ink2">since 12 August 1997</p>
        </div>

        <nav class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge" aria-labelledby="twr-17-ring">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc" id="twr-17-ring">The Retro Web Ring</h4>
          <ul class="list-none m-0 p-0 grid gap-2 min-w-0 sm:grid-cols-2">
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">&#171; Prev</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">Random</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">List</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">Next &#187;</button></li>
          </ul>
        </nav>

        <div class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc">Best viewed in</h4>
          <p class="m-0 justify-self-start px-3 py-2 bg-twr17-hot text-white font-twr17-mono text-[0.72rem] tracking-[0.08em] animate-twr17-pulse motion-reduce:animate-none">800 &times; 600 &middot; 256 colours</p>
          <p class="m-0 min-w-0 text-[0.72rem] leading-relaxed text-pretty text-twr17-ink2">The badge pulses on a two second cycle and stops under reduced motion. The era flashed it; that is the one thing not worth reproducing.</p>
        </div>
      </aside>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-twr17-face: #c8c8c8;
  --color-twr17-face2: #dcdcdc;
  --color-twr17-tileb: #bdbdbd;
  --color-twr17-hi: #ffffff;
  --color-twr17-sh: #7a7a7a;
  --color-twr17-ink: #16161a;
  --color-twr17-ink: oklch(0.20 0.006 280);
  --color-twr17-ink2: #4a4a52;
  --color-twr17-ink2: oklch(0.42 0.01 280);
  --color-twr17-acc: #0f3fa8;
  --color-twr17-acc: oklch(0.42 0.19 262);
  --color-twr17-hot: #a01414;
  --color-twr17-hot: oklch(0.43 0.18 27);
  --font-twr17-serif: Georgia, ui-serif, 'Times New Roman', Times, serif;
  --font-twr17-sans: Verdana, ui-sans-serif, Geneva, 'Trebuchet MS', system-ui, sans-serif;
  --font-twr17-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --animate-twr17-pulse: twr-17-pulse 2s ease-in-out infinite;
}
/* border-style: ridge derives its tones from border-color by an unspecified
   rule, so it differs per engine and occupies layout space. Four insets do not. */

@utility twr17-ridge {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-hi), inset -2px -2px 0 0 var(--color-twr17-sh), inset 4px 4px 0 0 var(--color-twr17-sh), inset -4px -4px 0 0 var(--color-twr17-hi);
}

@utility twr17-ridge-in {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-sh), inset -2px -2px 0 0 var(--color-twr17-hi), inset 4px 4px 0 0 var(--color-twr17-hi), inset -4px -4px 0 0 var(--color-twr17-sh);
}
/* A pulse, not a blink: 2s period, and it stops entirely under motion-reduce. */

@keyframes twr-17-pulse {
  0%,
    100% {
    transform: scale(1);
    box-shadow: 0 0 0 rgba(160, 20, 20, 0);
  }

  50% {
    transform: scale(1.03);
    box-shadow: 0 0 12px rgba(160, 20, 20, 0.55);
  }
}

.twr-17:has(#twr-17-theme:checked) {
  --color-twr17-face: #2a2a2e;
  --color-twr17-face2: #35353a;
  --color-twr17-tileb: #303034;
  --color-twr17-hi: #55555c;
  --color-twr17-sh: #17171a;
  --color-twr17-ink: #eeeef2;
  --color-twr17-ink: oklch(0.95 0.004 280);
  --color-twr17-ink2: #b0b0b8;
  --color-twr17-ink2: oklch(0.76 0.008 280);
  --color-twr17-acc: #8fb4ff;
  --color-twr17-acc: oklch(0.79 0.12 262);
  --color-twr17-hot: #ff8f8f;
  --color-twr17-hot: oklch(0.78 0.14 27);
}

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-twr17-face: #c8c8c8;
  --color-twr17-face2: #dcdcdc;
  --color-twr17-tileb: #bdbdbd;
  --color-twr17-hi: #ffffff;
  --color-twr17-sh: #7a7a7a;
  --color-twr17-ink: #16161a;
  --color-twr17-ink: oklch(0.20 0.006 280);
  --color-twr17-ink2: #4a4a52;
  --color-twr17-ink2: oklch(0.42 0.01 280);
  --color-twr17-acc: #0f3fa8;
  --color-twr17-acc: oklch(0.42 0.19 262);
  --color-twr17-hot: #a01414;
  --color-twr17-hot: oklch(0.43 0.18 27);
  --font-twr17-serif: Georgia, ui-serif, 'Times New Roman', Times, serif;
  --font-twr17-sans: Verdana, ui-sans-serif, Geneva, 'Trebuchet MS', system-ui, sans-serif;
  --font-twr17-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --animate-twr17-pulse: twr-17-pulse 2s ease-in-out infinite;
}
/* border-style: ridge derives its tones from border-color by an unspecified
   rule, so it differs per engine and occupies layout space. Four insets do not. */

@utility twr17-ridge {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-hi), inset -2px -2px 0 0 var(--color-twr17-sh), inset 4px 4px 0 0 var(--color-twr17-sh), inset -4px -4px 0 0 var(--color-twr17-hi);
}

@utility twr17-ridge-in {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-sh), inset -2px -2px 0 0 var(--color-twr17-hi), inset 4px 4px 0 0 var(--color-twr17-hi), inset -4px -4px 0 0 var(--color-twr17-sh);
}
/* A pulse, not a blink: 2s period, and it stops entirely under motion-reduce. */

@keyframes twr-17-pulse {
  0%,
    100% {
    transform: scale(1);
    box-shadow: 0 0 0 rgba(160, 20, 20, 0);
  }

  50% {
    transform: scale(1.03);
    box-shadow: 0 0 12px rgba(160, 20, 20, 0.55);
  }
}

.twr-17:has(#twr-17-theme:checked) {
  --color-twr17-face: #2a2a2e;
  --color-twr17-face2: #35353a;
  --color-twr17-tileb: #303034;
  --color-twr17-hi: #55555c;
  --color-twr17-sh: #17171a;
  --color-twr17-ink: #eeeef2;
  --color-twr17-ink: oklch(0.95 0.004 280);
  --color-twr17-ink2: #b0b0b8;
  --color-twr17-ink2: oklch(0.76 0.008 280);
  --color-twr17-acc: #8fb4ff;
  --color-twr17-acc: oklch(0.79 0.12 262);
  --color-twr17-hot: #ff8f8f;
  --color-twr17-hot: oklch(0.78 0.14 27);
}
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=Verdana&family=Georgia&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.
  • There is an irony this demo names rather than dodges: putting appearance in the markup is what <font> was reaching for. The difference is that utilities are compiled from @theme tokens with variants and a scale, not ad-hoc presentational HTML.
  • The ridge is an @utility because it appears on eight panels and needs an active: partner — the same test applied in demo 04, and the opposite conclusion from demo 13.
  • last:border-b-0 last:pb-0 replaces the CSS version's :last-child rule, so the entry separator logic sits on the entry.
  • The badge pulse is an --animate-* token with a motion-reduce:animate-none twin; retiming it is a token edit, not a markup edit.
  • The tiled ground is one arbitrary repeating-conic-gradient plus a [background-size:8px_8px] on the section — the tiled GIF with no file.
  • Colours stay @theme tokens even where each is used once, because the dark-ground flip re-points all of them in a single rule.
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 Nineties Web Guestbook Widget
Source: https://codefronts.com/design-styles/tailwind-retro-ui/tailwind-nineties-web-guestbook-widget/

A personal-homepage guestbook: odometer hit counter, webring navigation, signed entries and a &ldquo;best viewed in&rdquo; badge, on the grey ground and ridged borders of the era. The point it makes is the one that matters most about this period &mdash; reproduce the look, never the markup, because everything that made these pages work is now the reason they fail.
## HTML
```html
<section class="twr-17 relative block w-full min-h-svh box-border bg-twr17-face text-twr17-ink font-twr17-sans [background-image:repeating-conic-gradient(var(--color-twr17-tileb)_0%_25%,var(--color-twr17-face)_0%_50%)] [background-size:8px_8px]" aria-labelledby="twr-17-title">
  <h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-17-title">Nineties personal homepage guestbook</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-17-theme">
    <label class="grid place-items-center size-11 cursor-pointer bg-twr17-face2 text-twr17-ink2 twr17-ridge peer-focus-visible:outline-2 peer-focus-visible:outline-twr17-acc peer-focus-visible:outline-offset-2" for="twr-17-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 ground</span>
    </label>
  </div>

  <div class="grid grid-cols-[minmax(0,1fr)] gap-5 max-w-[64rem] mx-auto px-[clamp(0.75rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
    <header class="min-w-0 grid justify-items-center gap-1.5 text-center bg-twr17-face2 px-4 py-5 twr17-ridge">
      <h3 class="m-0 min-w-0 font-twr17-serif font-bold text-[clamp(1.5rem,1.1rem+2.4vw,2.6rem)] text-twr17-acc [text-shadow:1px_1px_0_var(--color-twr17-hi)]">Dave&rsquo;s Homepage</h3>
      <p class="m-0 font-twr17-mono text-[0.75rem] text-twr17-hot">~ under construction since 1997 ~</p>
    </header>

    <div class="grid grid-cols-[minmax(0,1fr)] gap-5 lg:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)] lg:items-start">
      <article class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge" aria-labelledby="twr-17-gb">
        <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc" id="twr-17-gb">Sign my guestbook</h4>
        <ol class="list-none m-0 p-0 grid gap-3 min-w-0">
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">AC</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Alex Chen</b> &middot; 14 Nov 1997 &middot; Manchester</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Great page! Love the MIDI. Come see my Star Trek shrine sometime.</p>
            </div>
          </li>
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">PS</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Priya Sharma</b> &middot; 2 Dec 1997 &middot; Leeds</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Added you to the ring. The tiled background is a bold choice and I respect it.</p>
            </div>
          </li>
          <li class="flex gap-3 min-w-0 border-b border-twr17-sh/40 pb-3 last:border-b-0 last:pb-0">
            <span class="flex-none grid place-items-center size-10 bg-twr17-acc text-white font-twr17-sans text-[0.75rem] font-bold twr17-ridge" aria-hidden="true">JL</span>
            <div class="min-w-0 grid gap-1">
              <p class="m-0 min-w-0 font-twr17-sans text-[0.72rem] text-twr17-ink2"><b class="font-bold text-twr17-ink">Jordan Lee</b> &middot; 19 Jan 1998 &middot; Bristol</p>
              <p class="m-0 min-w-0 font-twr17-serif text-[0.92rem] leading-normal text-pretty">Your links page is the best on the web. Signed, a fellow traveller.</p>
            </div>
          </li>
        </ol>
        <p class="m-0 min-w-0 text-[0.75rem] leading-relaxed text-pretty text-twr17-ink2">Entries are a real ordered list. No table, no spacer image, no <code class="font-twr17-mono bg-twr17-face px-1">&lt;font&gt;</code> anywhere in this widget.</p>
      </article>

      <aside class="min-w-0 grid content-start gap-5">
        <div class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc">Visitors</h4>
          <div class="flex gap-1 min-w-0" role="img" aria-label="Visitor count: 0 0 4 2 1 8 7">
            <span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">0</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">0</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">4</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">2</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">1</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">8</span><span class="grid place-items-center w-6 h-9 bg-[#111] text-[#7dff9a] font-twr17-mono text-[1.05rem] tabular-nums [box-shadow:inset_0_2px_4px_rgba(0,0,0,0.9)]">7</span>
          </div>
          <p class="m-0 text-[0.72rem] text-twr17-ink2">since 12 August 1997</p>
        </div>

        <nav class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge" aria-labelledby="twr-17-ring">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc" id="twr-17-ring">The Retro Web Ring</h4>
          <ul class="list-none m-0 p-0 grid gap-2 min-w-0 sm:grid-cols-2">
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">&#171; Prev</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">Random</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">List</button></li>
            <li><button class="w-full min-h-11 px-3 py-2 border-0 cursor-pointer font-twr17-sans text-[0.8rem] bg-twr17-face text-twr17-ink twr17-ridge active:twr17-ridge-in focus-visible:outline-2 focus-visible:outline-twr17-acc focus-visible:outline-offset-2" type="button">Next &#187;</button></li>
          </ul>
        </nav>

        <div class="min-w-0 grid content-start gap-3 bg-twr17-face2 p-[clamp(0.85rem,0.6rem+1vw,1.25rem)] twr17-ridge">
          <h4 class="m-0 font-twr17-serif text-[1rem] font-bold text-twr17-acc">Best viewed in</h4>
          <p class="m-0 justify-self-start px-3 py-2 bg-twr17-hot text-white font-twr17-mono text-[0.72rem] tracking-[0.08em] animate-twr17-pulse motion-reduce:animate-none">800 &times; 600 &middot; 256 colours</p>
          <p class="m-0 min-w-0 text-[0.72rem] leading-relaxed text-pretty text-twr17-ink2">The badge pulses on a two second cycle and stops under reduced motion. The era flashed it; that is the one thing not worth reproducing.</p>
        </div>
      </aside>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-twr17-face: #c8c8c8;
  --color-twr17-face2: #dcdcdc;
  --color-twr17-tileb: #bdbdbd;
  --color-twr17-hi: #ffffff;
  --color-twr17-sh: #7a7a7a;
  --color-twr17-ink: #16161a;
  --color-twr17-ink: oklch(0.20 0.006 280);
  --color-twr17-ink2: #4a4a52;
  --color-twr17-ink2: oklch(0.42 0.01 280);
  --color-twr17-acc: #0f3fa8;
  --color-twr17-acc: oklch(0.42 0.19 262);
  --color-twr17-hot: #a01414;
  --color-twr17-hot: oklch(0.43 0.18 27);
  --font-twr17-serif: Georgia, ui-serif, 'Times New Roman', Times, serif;
  --font-twr17-sans: Verdana, ui-sans-serif, Geneva, 'Trebuchet MS', system-ui, sans-serif;
  --font-twr17-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --animate-twr17-pulse: twr-17-pulse 2s ease-in-out infinite;
}
/* border-style: ridge derives its tones from border-color by an unspecified
   rule, so it differs per engine and occupies layout space. Four insets do not. */

@utility twr17-ridge {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-hi), inset -2px -2px 0 0 var(--color-twr17-sh), inset 4px 4px 0 0 var(--color-twr17-sh), inset -4px -4px 0 0 var(--color-twr17-hi);
}

@utility twr17-ridge-in {
  box-shadow: inset 2px 2px 0 0 var(--color-twr17-sh), inset -2px -2px 0 0 var(--color-twr17-hi), inset 4px 4px 0 0 var(--color-twr17-hi), inset -4px -4px 0 0 var(--color-twr17-sh);
}
/* A pulse, not a blink: 2s period, and it stops entirely under motion-reduce. */

@keyframes twr-17-pulse {
  0%,
    100% {
    transform: scale(1);
    box-shadow: 0 0 0 rgba(160, 20, 20, 0);
  }

  50% {
    transform: scale(1.03);
    box-shadow: 0 0 12px rgba(160, 20, 20, 0.55);
  }
}

.twr-17:has(#twr-17-theme:checked) {
  --color-twr17-face: #2a2a2e;
  --color-twr17-face2: #35353a;
  --color-twr17-tileb: #303034;
  --color-twr17-hi: #55555c;
  --color-twr17-sh: #17171a;
  --color-twr17-ink: #eeeef2;
  --color-twr17-ink: oklch(0.95 0.004 280);
  --color-twr17-ink2: #b0b0b8;
  --color-twr17-ink2: oklch(0.76 0.008 280);
  --color-twr17-acc: #8fb4ff;
  --color-twr17-acc: oklch(0.79 0.12 262);
  --color-twr17-hot: #ff8f8f;
  --color-twr17-hot: oklch(0.78 0.14 27);
}
```

How this works

Reproduce the look, never the markup. Everything the era used is now a defect: tables for layout destroy reading order, <blink> is unimplemented for good accessibility reasons, spacer GIFs are meaningless images in the accessibility tree, and <font> puts presentation beyond any user stylesheet. Utilities produce the identical visual with none of it — and there is a small irony worth naming: a class-per-property markup style is what 1997 was reaching for with <font> tags, except utilities are a compiled design system with tokens, variants and a scale rather than ad-hoc presentational HTML.

The ridged border is two insets, not a keyword. border-style: ridge derives its tones from border-color by an unspecified rule, so it differs per engine and shifts when the background changes. Four inset shadows give the same groove everywhere and cost no layout space, so hover and active states never reflow the widget. Because it appears on eight elements and needs an active: partner, it is an @utility rather than an inline value — the same judgement as demo 04.

The counter is the motif, and it should count something real. An odometer was a CGI script writing a number into a GIF. As styled digit wells the look survives and the number becomes selectable text, which is strictly better. If the count is decorative, label it as such; if it is real, it should be a live figure rather than a picture of one.

The badge is a pulse, not a blink. The era's badges flashed hard and fast, which is what the seizure threshold exists to prevent. Here it is a slow scale-and-glow on a two second period, stopped entirely under motion-reduce. It reads period-correct at a glance and puts nobody at risk — the trade this whole collection is built on.

Make it yours

  • Change the tiled ground by editing the section's arbitrary [background-size:...] — 8px reads as a coarse 90s tile, 4px as fine noise.
  • Swap --color-twr17-acc; the ridged borders are neutral and work against any hue.
  • Add a counter digit by copying one well — the odometer is a flex row with no fixed width.
  • Extend the webring to five links; the nav is a plain list and wraps on its own.
  • Retime or remove the badge pulse in the --animate-twr17-pulse entry without touching its styling.
  • Replace entry initials with drawn glyphs; they are grid cells with no image dependency.

Gotchas — read before shipping

  • Tables for layout, <blink>, <font> and spacer GIFs are the era's real techniques and every one is an accessibility failure today. Reproduce the appearance, not the source.
  • border-style: ridge and groove render differently in every engine and shift layout when toggled. Use the inset utility.
  • Grey-on-grey was everywhere in 1997 and most of those pairings sit near 3:1. Keep the ground and raise the ink.
  • A fast flashing badge is a genuine seizure risk. Slow it to a pulse and stop it under motion-reduce — nothing about the aesthetic requires the speed.
  • Do not confuse utilities with presentational HTML. Both put appearance in the markup, but one is a compiled token system and the other was a dead end; keeping colours as @theme tokens rather than raw hexes is what makes the difference real.
  • last:border-b-0 only works on the last element child. A trailing comment node is fine, but a trailing text node inside the list can break the assumption in some structures.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

repeating-conic-gradient for the tiled ground lands at Chrome 69 / Safari 12.1 / Firefox 83 and degrades to flat grey without it, which is if anything more period-accurate. Tailwind v4's generated sheet and @utility set the floor at Chrome 111+, Safari 16.4+, Firefox 128+.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Plain CSSno Tailwind surface

HTML & JSno Tailwind involved

Search CodeFronts

Loading…