Skip to content →

Get started

Getting a retrostrap page on the air takes two tags and one opinion (the theme). Everything on this page is copy-paste ready, that is a design goal, not an accident.

The two-tag install (CDN)

Put these in your <head> and you are a webmaster:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap.min.js"></script>

The CSS is the framework; the script is optional enhancement (dialogs, the theme API, the auditor), every component works without it. Two optional extras:

<!-- a theme (classic is built in; see "Pick a theme" below) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/themes/midnight.css">

<!-- tiled backgrounds: stars, checkers, hearts, dither bands … -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap-patterns.css">

The examples pin an exact version on purpose: machines and webmasters both deserve URLs that don't move under them. A range like @0 rides along with new releases, handy on a sandbox page, shaky under a homepage you love.

Or install from npm

npm install retrostrap
import "retrostrap/retrostrap.min.css";
import { Retrostrap } from "retrostrap";

Same files, same behavior, zero dependencies either way.

Using a SPA framework (React, Vue, Svelte, Astro)

The whole integration contract is two calls: Retrostrap.init(container) after your framework mounts new DOM, Retrostrap.destroy(container) before it unmounts. Enhancers never move your nodes and dialogs use the native top layer (no portals), so the virtual DOM never gets surprised. For React, that contract fits in one hook, copy it as is:

import { useEffect, useRef } from "react";
import "retrostrap/retrostrap.min.css";
import { Retrostrap } from "retrostrap";

function useRetrostrap() {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    Retrostrap.init(el);
    return () => Retrostrap.destroy(el);
  }, []);
  return ref;
}

function Shoutbox() {
  const ref = useRetrostrap();
  return (
    <div ref={ref} className="rs-panel">
      <h2 className="rs-panel__title">Shoutbox</h2>
      <div data-rs-widgets="guestbook"></div>
    </div>
  );
}

init is idempotent and destroy is real teardown, so React 18's StrictMode double-mount is harmless. In Next.js, widgets are client work: put the hook in a "use client" component. The other frameworks are the same two calls in their own coats:

// Vue
onMounted(() => Retrostrap.init(root.value));
onBeforeUnmount(() => Retrostrap.destroy(root.value));

// Svelte
onMount(() => {
  Retrostrap.init(node);
  return () => Retrostrap.destroy(node);
});

Astro needs even less: retrostrap markup is plain HTML, so most pages ship with zero islands; put widgets inside a client:load island or just keep the deferred script tag.

One caveat about widgets and re-renders. Overlay widgets (the cat, the sparkles, the starfield) live on document.body, outside your component tree, and destroy() cleans them up; self-rendering widgets (guestbook, jukebox, clock) own the host div you give them, so declare it with no children and they never collide with the virtual DOM. The only shared ground is text that both sides want to own: smilies rewrites your text nodes, so if that text changes from state, fire Retrostrap.emit(el, "rs:content") in the same effect and smilies re-walks the subtree (the guestbook uses the same handshake). And a marquee clones its track for the seamless loop, so keep marquee text static, or destroy and re-init that node when it truly must change.

That is the entire story, there is no wrapper package to install. If you would rather have one anyway, say so on the Boards; we would only build thin wrappers if there is real demand for them.

The page skeleton

The era's anatomy: the <body> wears the wallpaper, and your content sits on an opaque sheet, the "centered table on a starfield" look. rs-page is the sheet, rs-container is the width governor (760px, the honest width of an 800×600 CRT), and a layout recipe gives the page its shape. Here is a complete homepage:

<!doctype html>
<html lang="en" data-rs-theme="midnight">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My corner of the web</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/themes/midnight.css">
  <script defer src="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap.min.js"></script>
</head>
<body>
  <a class="rs-skip" href="#main">Skip intro →</a>
  <div class="rs-page rs-container">
    <div class="rs-layout rs-layout--sidebar-left">

      <header class="rs-layout__header">
        <h1>~ Welcome to my homepage ~</h1>
        <nav class="rs-navbar" aria-label="Site">
          <a href="#" aria-current="page">Home</a>
          <a href="#">About me</a>
          <a href="#">Links</a>
          <a href="#">Guestbook</a>
        </nav>
      </header>

      <main class="rs-layout__main" id="main">
        <h2>What's new</h2>
        <p>Added three new wallpapers to the downloads section!
           <sup><span class="rs-badge rs-badge--new">NEW!</span></sup></p>
      </main>

      <nav class="rs-layout__nav rs-sidenav" aria-label="Sections">
        <h3 class="rs-sidenav__section">Main</h3>
        <ul>
          <li><a href="#">news archive</a></li>
          <li><a href="#">my scope</a></li>
          <li><a href="#">cool links</a></li>
        </ul>
      </nav>

      <footer class="rs-layout__footer rs-footer">
        <p class="rs-footer__updated">Last updated: today, obviously.</p>
      </footer>

    </div>
  </div>
</body>
</html>

Notes worth their pixels:

Pick a theme

One attribute on <html> picks the outfit; one stylesheet supplies it. Do not mix theme files, one theme per page, like nature intended.

Theme The look Extra stylesheet
classic 1996 browser default: silver page, Times, blue links none, built into the core
midnight GeoCities after dark: starfield, neon text themes/midnight.css
bevel the system desktop: teal, silver chrome, Tahoma themes/bevel.css
phosphor green terminal on black, Courier everywhere themes/phosphor.css
<html data-rs-theme="midnight">

Switching at runtime, for a theme picker like the one in our header, goes through the JavaScript API, which also remembers the choice in localStorage:

Retrostrap.theme.set("midnight");

If you switch themes at runtime, make sure the theme's stylesheet is on the page (load it up front, or swap a <link>'s href the way this site does, view source, it's all there). Full tour with live swatches on the themes page.

Comfy mode

The era's text was authentically tiny. rs-comfy on <html> steps body text one size up the scale and relaxes the line-height, mercy, on demand, without breaking the look:

<html data-rs-theme="midnight" class="rs-comfy">

There's a toggle in this site's header if you want to feel the difference.

Verify with Retrostrap.audit()

The five laws are machine-checkable. Open your console on any page you've built and run:

Retrostrap.audit();

It walks your styled elements and reports off-palette colors, rounded corners, blurred shadows, illegal easing, un-sanctioned fonts, and links that lost their underlines, as a console table plus a JSON report, with hints written to be actionable ("off palette; nearest legal is #FF9966"). Clean output looks like this:

[retrostrap] audit clean, 212 elements, all lawful. The year holds at 1999.

The same laws ship as data in guardrails.json if you'd rather enforce them in your own tooling.

Where to next