A Modern Take on the Webring

The webring is a charming artifact of the early web. But the core idea—a federated collection of sites that link to one another—is still a solid one. The question is how to build that experience today without burdening every participating site owner with a pile of custom code.

At its heart, a webring needs to do a few things: it identifies a site as part of a group, lets visitors hop to the next or previous member, and perhaps offers a random site or a full list. The trick is making it easy for each site owner to add. Ideally, they just paste a snippet into their page and it works.

Let's explore the modern options for that, using a webring for CSS-focused sites as our example.

Embedding Options: iframe vs. Web Component

The old-school approach might have been a frameset. Today, a classic way to embed a widget is with an <iframe>, much like YouTube's embed code. It’s easy, but iframes render an entire extra document, which is a performance hit. They also offer little room for customization, and passing the current site’s identity into the iframe—via a URL parameter or postMessage—can get messy.

A more modern solution is a Web Component. You can define a custom element like <webring-*> where the current site is passed directly as an attribute. This provides a much cleaner interface.

<webring-css site="https://css-tricks.com">
  This is gonna boot itself up into webring in a minute.
</webring-css>

Storing the Ring's Data

For the webring to be maintained, sites need to be added or removed without requiring every other member to update their code. The ring’s data needs to live in one, universally accessible place.

A simple JSON file hosted on GitHub is a perfect fit for this kind of low-volume data. It acts as a central, queryable registry that can be updated via pull request.

The web component then fetches this data when it initializes on the page:

fetch(`https://raw.githubusercontent.com/CSS-Tricks/css-webring/main/webring.json`)
  .then((response) => response.json())
  .then((sites) => {
     // Got the data.
  });

Scaffolding the Component

Setting up the component involves loading that data source when the element is mounted:

const DATA_FOR_WEBRING = `https://raw.githubusercontent.com/CSS-Tricks/css-webring/main/webring.json`;

const template = document.createElement("template");
template.innerHTML = `
<style>
  /* styles */
</style>

<div class="webring">
  <!-- content -->
</div>`;

class WebRing extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    fetch(DATA_FOR_WEBRING)
      .then((response) => response.json())
      .then((sites) => {
        // update template with data
      });
  }
}

window.customElements.define("webring-css", WebRing);

From there, the rest of the process breaks down into a handful of logical steps:

  1. Read the attribute from the custom tag to determine the current site.
  2. Look up that site in the fetched JSON dataset.
  3. Generate the navigation links for previous, next, and random entries.
  4. Inject that generated HTML into the component's shadow DOM or template.

The result is a functional navigation widget that site owners can drop into their pages with a single, self-contained tag—handling data, presentation, and logic without requiring any additional scripts to be loaded by the host page. There is certainly room for improvements like error handling and better default styling, but this setup proves the concept of a webring can be adaptive and lightweight.