Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

90s Components

Every trope of the 1990s web, encapsulated as a standards-compliant Custom Element.

32 components for decorating a page, and a site layer that turns them into a whole website — themes, a shared masthead, navigation that knows what page you are on, and a scaffolder that writes the site for you.

No dependencies, no build step, no framework. Each component is a single self-registering file that uses Shadow DOM, so nothing leaks into your page and your page's CSS can't accidentally un-1997 it.

Live demo →

The 90s Components homepage: arched WordArt title, a scrolling marquee, a rainbow divider, a beveled button bar and an animated under-construction sign

<script type="module" src="components/index.js"></script>

<retro-marquee speed="8">Welcome to my awesome homepage!!!</retro-marquee>
<hit-counter digits="6" label="visitors since 1997"></hit-counter>
<under-construction></under-construction>

Or take only what you need — the files have no shared imports:

<script type="module" src="components/retro-counter.js"></script>

Building a whole site

One command writes a complete, standalone website — pages, navigation, theme and a copy of the library:

npx 90s-new-site my-site --title "Dave's Homepage" --theme geocities
cd my-site && python3 -m http.server 8000

See the example site →  ·  five pages, six themes, built from the tags below.

Dave's Homepage: a five-page site with a WordArt masthead, navigation bar, sidebar with a hit counter and theme picker, a what's-new list and a full footer with webring and badge shelf

How a page is put together

Every page is ordinary HTML with a shell around it. The masthead, navigation and footer read from one config file, so a page added there appears everywhere.

<script type="module" src="site.js"></script>

<retro-page theme="geocities" layout="sidebar-left">
  <retro-header slot="header"></retro-header>
  <site-nav slot="nav" align="center"></site-nav>

  <retro-sidebar slot="sidebar" title="About this site">
    <hit-counter digits="6" per-session></hit-counter>
    <theme-picker></theme-picker>
  </retro-sidebar>

  <h2>Welcome to my home page!</h2>
  <p>Ordinary HTML goes here, themed to match.</p>

  <retro-footer slot="footer"></retro-footer>
</retro-page>
// site.js — the whole site, declared once
import { defineSite } from './site/index.js';

defineSite({
  title: "Dave's Homepage",
  tagline: 'Cats, MIDI files, and the information superhighway',
  email: 'dave@example.com',
  pages: [
    { href: 'index.html', label: 'Home' },
    { href: 'links.html', label: 'Cool Links' },
  ],
  badges: ['netscape', 'notepad', 'html'],
  webring: { name: 'The Personal Homepage Ring', prev: '#', next: '#' },
});

Themes

A theme is a set of CSS custom properties plus a background, applied to :root. Because custom properties cross shadow boundaries, one attribute restyles the whole page — components included.

Theme Looks like
geocities Navy starfield, gold headings, yellow marquee
angelfire Pastel pink hearts, Comic Sans, cream panels
win95 Teal desktop, grey chrome, MS Sans Serif
hacker Black, green monospace, matrix rain behind everything
vaporwave Purple grid, hot pink and cyan
netscape Grey and white, Times, blue underlined links

The same page rendered three ways: pastel pink Angelfire, teal-and-grey Windows 95, and a black-and-green terminal theme

The same page and the same markup, in three of the six themes.

Drop in a <theme-picker> and visitors can switch between them; the choice is remembered across pages.

The site tags

Tag What it does Key attributes
<retro-page> The shell: theme, background, centred column, optional sidebar theme layout width background sidebar-width no-frame
<retro-header> Masthead — WordArt title, tagline, marquee title tagline marquee preset shape no-marquee
<retro-footer> Badge shelf, counter, globe, e-mail, webring, last-updated email badges counter-key updated no-counter no-ring
<site-nav> The site's navigation, marking the current page title align vertical exclude
<retro-sidebar> The narrow column of boxes down one side title sticky
<retro-panel> A titled content box title icon variant align
<theme-picker> Switches the theme, remembered in localStorage label target storage-key no-remember
<photo-gallery> Thumbnail grid with a click-to-enlarge viewer columns thumb no-captions no-viewer
<link-list> The "cool links" page, with NEW! flags on recent entries bullet columns new-for-days no-numbers
<whats-new> The dated update log, sorted newest first title max new-for-days no-sort

Scaffolder options

node bin/new-site.mjs <directory> [options]

  --title      Site title, shown as WordArt in the masthead
  --tagline    One line under the title
  --theme      geocities | angelfire | win95 | hacker | vaporwave | netscape
  --pages      Comma separated slugs (default: home,about,links,gallery,guestbook)
  --email      Address for the footer's e-mail link
  --webmaster  Credited in the "last updated" line
  --marquee    Scrolling text in the masthead
  --force      Write into a directory that already exists

Slugs named home, about, links, gallery, guestbook and contact get starter content suited to each; anything else gets a blank page. The result is static files — publish them anywhere that serves a directory.

The shelf

The classics

Six windows showing a scrolling marquee, blinking text, three hit-counter themes, an under-construction sign, animated dividers and a last-updated line with a blinking NEW! flag

Tag What it was Key attributes
<retro-marquee> Scrolling text, via CSS animation rather than the tag Netscape gave us speed direction behavior bgcolor color pause-on-hover
<blink-text> The <blink> tag, restored to active duty speed duty color
<hit-counter> "You are visitor number 000042" — real counts in localStorage, odometer roll key start digits theme label per-session static
<under-construction> Animated barrier stripes and a digging workman text sub worker no-stripes no-blink
<retro-hr> The animated rainbow divider bar variant height animated
<last-updated> "This page last updated on…" with a blinking NEW! flag date prefix by new-for-days

Text effects

Six windows showing chrome and arched WordArt, rainbow gradient text, flaming and frozen headline text, dancing emoji, three wireframe globes and flapping envelope mailto links

Tag What it was Key attributes
<word-art> Extruded, arched, chrome-plated headline art preset shape size depth
<rainbow-text> A gradient per line, or a hue per letter mode speed direction bold
<fire-text> Flaming headline text, no GIF89a required size speed cool
<dancing-banana> Peanut butter jelly time emoji count size speed style-name
<spinning-globe> The rotating wireframe Earth beside the email link size color speed glow
<email-me> The flapping envelope mailto link email subject label size

Windows and chrome

Seven windows showing draggable window chrome, a modal dialog, a beveled nav bar, a segmented progress bar, a seven-segment clock, right-click blocking and a two-pane frameset

Tag What it was Key attributes
<retro-window> Grey chrome, ridged borders, a title bar you can drag title icon width draggable-window sunken no-buttons
<popup-alert> The modal dialog box — without freezing the browser title message icon ok-text cancel-text backdrop
<retro-nav> The beveled grey button bar; slot in plain <a> elements title align vertical
<retro-progress> "Loading… please wait" in chunky segments value duration loop segments label done-text
<retro-clock> The seven-segment JavaScript clock format seconds show-date color size
<retro-frames> The <frameset> layout, with a draggable divider nav-width side height
<no-right-click> Image "protection". It never worked. It still doesn't. message target no-select no-drag

Backgrounds and cursors

Four windows showing a canvas-drawn brick tile, a twinkling star field with a shooting star, falling green matrix glyphs and the sparkle cursor trail

Tag What it was Key attributes
<tiled-background> The seamless repeating tile, drawn on a canvas pattern size color bg opacity contained
<starry-night> Twinkling star field, optionally with shooting stars density color speed shooting contained
<matrix-rain> Falling green glyphs, arriving in 1999 color speed font-size glyphs contained
<sparkle-cursor> The mouse trail script pasted into a million pages colors size life rate gravity emoji

Guestbooks, rings and badges

A signed guestbook, a webring navigation box, eight 88x31 badges, a gold Cool Site of the Day rosette and a 468x60 banner ad

Tag What it was Key attributes
<retro-guestbook> "PLEASE SIGN MY GUESTBOOK!!", stored in localStorage key title max placeholder no-form
<web-ring> Prev / Random / Next through somebody's ring name owner prev next random hub index total list
<retro-badge> The 88x31 button — ten presets, or roll your own preset label sub colors text-color href blink
<site-award> "Cool Site of the Day", awarded by someone's cousin title from date stars href ribbon-color
<retro-banner> The 468x60 banner ad, sweeping gloss included text sub cta href colors flash
<office-helper> The animated assistant with opinions about your letter messages interval name position
<status-scroller> The message that crawled along the status bar text messages mode speed position inline

Things that make noise

The MIDI jukebox player and the dial-up modem mid-handshake, showing CONNECT 33600, above the page footer with its award, hit counter, spinning globe, clock and badge shelf

Tag What it was Key attributes
<midi-jukebox> The auto-playing background MIDI, rebuilt in Web Audio tune tempo volume wave loop
<dial-up-modem> Dial tone, DTMF, ring, carrier, CONNECT 33600 number speed volume no-sound

Notes on the details

Nothing is downloaded. No GIFs, no MIDI files, no WAVs, no fonts. Tiles are drawn on a canvas and inlined as data URIs; the flames, globes and star fields are CSS and canvas; the music and the modem handshake are synthesised with Web Audio. The whole library is smaller than one of the animated GIFs it replaces.

Reduced motion is respected. Every animation is wrapped in @media (prefers-reduced-motion: reduce), and the canvas components check matchMedia before starting a loop. The nostalgia stops moving for anyone who asks it to.

Sound needs a gesture. <midi-jukebox> and <dial-up-modem> both build their audio graph on click, because browsers no longer let a page start screeching on load. This is the one improvement on 1997 that nobody argues with.

Guestbook entries are text, not HTML. Every visitor-supplied string is inserted with textContent or as a text node, and URLs are checked for an http(s): scheme before being turned into links.

Timers and listeners are cleaned up in disconnectedCallback, so removing an element from the DOM stops its animation frames, intervals and document-level listeners.

The site layer shares two modules. Every component in components/ is standalone, but site/ is not: the theme and the site config are shared state by definition — a site is more than one page. site/retro-theme.js and site/site-config.js are the only two modules anything imports.

One file is named oddly on purpose. <hit-counter> lives in components/retro-counter.js, because ad blockers block any request for a file called hit-counter.js on sight — which, in fairness, is the most authentically 1990s failure mode this library has.

Styling hooks. Components expose part names (::part(counter), ::part(badge), ::part(window) and so on) plus a handful of CSS custom properties, so you can restyle them from outside the shadow root without forking the file.

Events and APIs

A few components do more than sit there:

// Hit counter driven by a real server-side number
document.querySelector('hit-counter').set(1_048_576);

// Modal dialog that actually returns something
const confirmed = await document.querySelector('popup-alert').show();

// Guestbook, programmatically
book.sign({ name: 'Dave', message: 'Cool page!!!', url: 'http://example.com' });
book.addEventListener('guestbook-signed', (e) => console.log(e.detail));

// The window chrome's close button is cancellable
win.addEventListener('window-close', (e) => e.preventDefault());

Browser support

Anything with Custom Elements v1, Shadow DOM v1 and ES modules — every current version of Chrome, Edge, Firefox and Safari. Notably not Netscape Navigator 3.0.

Running the demo locally

ES modules need to be served over HTTP rather than opened from the filesystem:

python3 -m http.server 8000
# then open http://localhost:8000/

Licence

MIT. See LICENSE.

About

Every trope of the 1990s web, encapsulated as a standards-compliant Custom Element. 32 components, no dependencies, no build step.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages