90s COMPONENTS

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

♫ WELCOME TO MY AWESOME HOMEPAGE!!! ♫ 32 components ♫ zero dependencies ♫ zero build step ♫ works in every modern browser ♫ sign my guestbook!!! ♫
The Classics Text Effects Windows & Chrome Backgrounds Guestbooks & Rings Things That Make Noise Source

Getting started

No build step, no dependencies, no framework. Drop in the whole shelf:

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

<retro-marquee speed="8">Welcome to my homepage!!!</retro-marquee>
<hit-counter digits="6" start="1"></hit-counter>

Or take only what you need β€” every component is a single self-registering file with no shared imports:

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

Every component honours prefers-reduced-motion, so the nostalgia stops moving for anyone who asks it to.

Building a whole site

The components above decorate a page. The site layer 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:

npx 90s-new-site my-site --title "Dave's Homepage" --theme geocities

▶ See the example site β€” five pages and six themes, built from these components. Read the docs »

▶ The Classics

The scrolling marquee

<retro-marquee>
This text will not sit still and you cannot make it.
← other way →
speed seconds · direction left|right|up|down · behavior scroll|alternate|slide · bgcolor · color · height · pause-on-hover
<retro-marquee speed="7" pause-on-hover>
  Welcome to my homepage!!!
</retro-marquee>

The blink tag, restored

<blink-text>
NEW! FAST BLINK slow and thoughtful
speed ms per cycle · duty 0-1 share of the cycle spent visible · color
<blink-text speed="400" color="#c00">NEW!</blink-text>

You are visitor number…

<hit-counter>


Counts real visits in localStorage and rolls the digits like an odometer. Lives in components/retro-counter.js β€” ad blockers block any file called hit-counter.js on sight. key · start · digits · theme odometer|lcd|pixel|flip · label · per-session · static. Call .set(n) to drive it from a real server count.
<hit-counter digits="6" per-session
             label="visitors since 1997"></hit-counter>

Pardon our dust

<under-construction>
text · sub · worker emoji · no-stripes · no-blink
<under-construction
  text="THIS PAGE IS NOT FINISHED"></under-construction>

The animated divider

<retro-hr>
variant rainbow|bar|fire|dots|stars|lightning · height · animated
<retro-hr variant="rainbow" animated></retro-hr>

Last updated …

<last-updated>

Shows a blinking NEW! flag only while the date really is recent. date · prefix · by · new-for-days · no-flag
<last-updated date="1998-04-12"
              by="the Webmaster"></last-updated>

▶ Text Effects

Extruded headline art

<word-art>
CHROME ARCHED WAVY
preset rainbow|chrome|purple|sunset|marble|blues · shape flat|arch|wave · size · depth
<word-art preset="chrome" shape="arch">
  MY HOMEPAGE
</word-art>

Taste the rainbow

<rainbow-text>
A flowing gradient of colour

One hue per letter
mode gradient|letters · speed seconds · direction · bold
<rainbow-text mode="letters" bold>
  COOL LINKS
</rainbow-text>

Flaming text, no GIF

<fire-text>
HOT!     COLD
size · speed seconds · cool for blue flames
<fire-text size="3rem">DOWNLOADS</fire-text>

Peanut butter jelly time

<dancing-banana>
emoji · count · size · speed seconds · style-name dance|shimmy|spin
<dancing-banana count="7"></dancing-banana>

The rotating wireframe Earth

<spinning-globe>
size · color · speed seconds per rotation · glow="off"
<spinning-globe size="70" color="#0cf"></spinning-globe>

The flapping envelope

<email-me>


email · subject · label · size
<email-me email="webmaster@example.com"
          subject="I visited your homepage!"></email-me>

▶ Windows & Chrome

Grey box, ridged border

<retro-window>
Drag me by the title bar. The close button fires a cancellable window-close event first. 3 object(s) 1.44 MB
Every panel on this page is one. title · icon · width · draggable-window · no-buttons · sunken · active="false". Slot status for the status bar. Events: window-close, window-minimize, window-maximize.
<retro-window title="My Documents" draggable-window>
  Hello!
  <span slot="status">3 object(s)</span>
</retro-window>

The modal dialog

<popup-alert>
Non-blocking, unlike the real thing. .show() returns a promise resolving true/false. title · message · icon info|warning|error|question · ok-text · cancel-text · backdrop · open
const ok = await popup.show();
if (ok) console.log('They clicked OK');

The beveled button bar

<retro-nav>
Slot in ordinary <a> elements. title · align left|center|right · vertical
<retro-nav align="center">
  <a href="/">Home</a>
  <a href="/links">Links</a>
</retro-nav>

Loading, please wait

<retro-progress>
value 0-100 · duration seconds to auto-fill · loop · segments · color · label · done-text. Set .value to drive it yourself; fires progress-complete.
<retro-progress duration="9" loop
  label="Downloading winsock.dll ..."></retro-progress>

The seven-segment clock

<retro-clock>
format 12|24 · seconds · show-date · color · size · label
<retro-clock seconds show-date></retro-clock>

Image "protection"

<no-right-click>

Try right-clicking inside this box:

📷 My precious original artwork
Preserved for historical interest. It never worked; it still doesn't. message · title · target CSS selector · no-select · no-drag. Fires right-click-blocked.
<no-right-click target="#gallery" no-drag></no-right-click>

The frameset layout

<retro-frames>
Contents

The content frame

Two panes, a draggable divider, and no way to bookmark the page you are actually looking at. Drag the divider, or focus it and use the arrow keys.

Your browser does not support frames. It is 1996 and this is your fault.

nav-width · side left|right|top · height. Slots: nav, default, noframes.
<retro-frames nav-width="150" height="200">
  <div slot="nav">...</div>
  <p>Main content</p>
</retro-frames>

▶ Backgrounds & Cursors

The seamless tile

<tiled-background>
Tiles are drawn on a canvas, so the page stays lighter than one 1996 JPEG. This page uses the star pattern. pattern stars|bricks|clouds|hearts|plaid|noise|grid|camo|circuits · size · color · bg · opacity · contained
<tiled-background pattern="stars"
  color="#5577cc" bg="#000022"></tiled-background>

Twinkling star field

<starry-night>
Fixed behind the whole page by default; add contained to keep it inside a positioned parent. density · color · speed · shooting for shooting stars
<starry-night density="1.5" shooting></starry-night>

Falling green glyphs

<matrix-rain>
1999 arrived and every homepage suddenly had these. color · speed · font-size · glyphs · opacity · contained
<matrix-rain color="#00ff41"></matrix-rain>

The mouse trail

<sparkle-cursor>

It is already running β€” move your mouse anywhere on this page.

The script that was pasted into a million pages. colors comma separated · size · life ms · rate 0-1 · gravity · emoji to trail emoji instead of dots
<sparkle-cursor emoji="✨" rate="0.5"></sparkle-cursor>

▶ Guestbooks, Rings & Badges

PLEASE SIGN MY GUESTBOOK!!

<retro-guestbook>
Entries are stored in localStorage β€” roughly as durable as the CGI scripts these used to post to, and they never leave the browser. Visitor text is inserted as text nodes, never as HTML. key · title · max · placeholder · no-form. API: .sign({name, message, url}), .entries, .clear(). Fires guestbook-signed.
<retro-guestbook key="my-page" max="50"></retro-guestbook>

Member of a webring

<web-ring>
name · owner · prev · next · random · hub · index · total · list a comma separated set of URLs for a client-side random hop
<web-ring name="The Geocities Memorial Ring"
  prev="..." next="..." index="14" total="212"></web-ring>

The 88x31 button shelf

<retro-badge>
Ten presets, or roll your own. preset netscape|ie|notepad|html|geocities|css|800x600|dialup|ring|y2k · label · sub · colors · text-color · href · blink
<retro-badge preset="netscape" blink></retro-badge>
<retro-badge label="Powered by" sub="COFFEE"
  colors="#3b2314,#7b4a2b"></retro-badge>

Cool site of the day

<site-award>
title · from · date · href · stars 0-5 · ribbon-color
<site-award title="COOL SITE OF THE DAY"
  from="Dave's Web Reviews" stars="5"></site-award>

The 468x60 banner ad

<retro-banner>
Standard IAB full banner, complete with sweeping gloss. text · sub · cta · href · colors · flash
<retro-banner flash href="/prize"></retro-banner>

The unwanted assistant

<office-helper>

He is in the bottom right corner of this page, and he has opinions. Click the × to make him leave.

messages pipe separated · interval ms · name · position bottom-right|bottom-left|inline. API: .say(text), .dismiss(). Fires helper-dismissed.
<office-helper interval="9000"
  messages="It looks like you are browsing.|Need help?">
</office-helper>

The status bar crawl

<status-scroller>
The one at the bottom of this page types itself out. text · messages pipe separated · mode type|scroll · speed ms · width · position bottom|top · inline
<status-scroller mode="type"
  messages="Welcome!|Best viewed at 800x600"></status-scroller>

▶ Things That Make Noise

The background MIDI

<midi-jukebox>
Synthesised with Web Audio, so there is no plugin and no .mid file. Browsers require a click before sound, which is the one improvement on 1997 that nobody argues with. tune ode-to-joy|fur-elise|greensleeves|entertainer|canon|scale · tempo · volume · wave · loop. API: .play(), .stop().
<midi-jukebox tune="fur-elise" tempo="120" loop>
</midi-jukebox>

The handshake

<dial-up-modem>
Dial tone, DTMF digits, ring, answer tone, scrambled handshake, carrier β€” then CONNECT. All synthesised; nothing is downloaded. number · speed · volume · no-sound. API: .dial(), .hangUp(). Fires modem-connected / modem-disconnected.
<dial-up-modem number="555-0134"
               speed="33600"></dial-up-modem>