Webring List
A <nav> landmark for indie webrings — a prev / next sibling strip plus a full directory of every site in the ring. Order of the sites prop is the order of the ring; exactly one entry flagged current: true anchors the siblings and gets the active highlight.
Customize
Shape
4
Installation
npx shadcn@latest add https://craftbits.dev/r/webring-list.jsonUsage
Pass an ordered ring of sites. Flag exactly one as current: true — its neighbours become the prev / next siblings shown above the directory. The ring wraps, so the site after the last entry is the first entry, and vice versa.
import { WebringList } from "@craft-bits/core";
<WebringList
sites={[
{ id: "alice", name: "Alice", url: "https://alice.example" },
{ id: "me", name: "My Site", url: "https://me.example", current: true },
{ id: "bob", name: "Bob", url: "https://bob.example" },
]}
/>Render only the compact sibling strip — useful at the foot of an article — by turning the directory off:
<WebringList sites={sites} showDirectory={false} />Render only the catalog — useful on a standalone "all members" page — by turning the sibling row off:
<WebringList sites={sites} showSiblings={false} />Understanding the component
- Sibling computation.
WebringListfinds the entry flaggedcurrent: true, then picks the previous and next sites in the array — with wrap-around at both ends. The order of the array is the order of the ring, so the consumer controls direction. - Two stacked regions. The
<nav>renders a sibling strip on top (prev label, arrow, name; mirror on the right for next) and the directory below. Each region is its own subcomponent, so consumers can render either in isolation viaWebringList.SiblingsorWebringList.Directory. - Current entry hook. In the directory, the matching
<li>gets thecb-accentborder and its link carriesaria-current="page". Assistive tech announces it as "current," and sighted users see the distinct highlight. - External by default. Every ring link defaults to
target="_blank"withrel="noopener noreferrer"— webring members are by definition other people's sites. Override both attributes per call site, or passasChildto delegate routing to your framework's link primitive. - Empty + single-site states. With no sites, the component renders the
emptyStateslot (defaulting to a single muted line). With one site, the sibling row collapses to two em-dashes because a ring of one has no siblings. - Focus visible. Each ring link gets a
focus-visible:ring keyed to--cb-accent, offset from--cb-bg, so keyboard users always see the current target on every theme surface.
Props
WebringList
| Prop | Type | Default | Description |
|---|---|---|---|
sites | readonly WebringSite[] | — | Ordered ring of sites. Flag exactly one with current: true to anchor siblings. |
aria-label | string | "Webring" | Accessible name for the <nav> landmark. |
emptyState | ReactNode | muted line | Rendered when sites is empty. |
showSiblings | boolean | true | Show the prev / next row above the directory. |
showDirectory | boolean | true | Show the full directory list of sites. |
prevLabel | ReactNode | "Prev" | Label rendered before the previous-sibling link. |
nextLabel | ReactNode | "Next" | Label rendered before the next-sibling link. |
className | string | — | Merged onto the rendered <nav>. |
...rest | HTMLAttributes<HTMLElement> | — | Any other <nav> prop. |
WebringSite
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier — used as the React key. |
name | ReactNode | Display name. |
url | string | Destination URL. Opened in a new tab by default. |
description | ReactNode | Optional one-line tagline rendered under the name in the directory. |
current | boolean | Mark the consumer's own entry. Exactly one site should be flagged. |
WebringList.Siblings
| Prop | Type | Default | Description |
|---|---|---|---|
prev | WebringSite | null | — | Previous site in the ring, or null when none. |
next | WebringSite | null | — | Next site in the ring, or null when none. |
prevLabel | ReactNode | "Prev" | Label before the previous-sibling link. |
nextLabel | ReactNode | "Next" | Label before the next-sibling link. |
className | string | — | Merged onto the rendered <div>. |
WebringList.Directory
| Prop | Type | Default | Description |
|---|---|---|---|
sites | readonly WebringSite[] | — | Ordered ring of sites. |
currentIndex | number | -1 | Index of the entry to highlight; -1 for no highlight. |
className | string | — | Merged onto the rendered <ul>. |
WebringList.Link
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as the child element instead of an <a> (Radix Slot). |
target | string | "_blank" | Anchor target. Override to keep navigation in-tab. |
rel | string | "noopener noreferrer" | Anchor rel. |
className | string | — | Merged onto the rendered element. |
...rest | AnchorHTMLAttributes<HTMLAnchorElement> | — | Any other anchor prop. |
Accessibility
- The root renders
<nav aria-label="Webring">so assistive tech announces the region. Override with thearia-labelprop for a more specific name. - The current directory entry carries
aria-current="page"and gets a stronger border so both sighted and assistive-tech users know it is the consumer's own entry. - Sibling arrows are wrapped in
aria-hiddenspans because the directional cue is already in the visible "Prev" / "Next" labels and the announced link names. - Each sibling link has a richer accessible name —
"Previous site: Alice's Garden"— built fromaria-labelso the announcement carries direction plus destination even when the visible link text is short. - Every ring link gets a
focus-visible:ring keyed to--cb-accent, offset from--cb-bg, so keyboard users always see the current target on every theme surface. - Color contrast in the default theme: directory link text uses
--cb-accentagainst--cb-surface, the current entry uses--cb-fg, and sibling labels use--cb-fg-subtle— all pass WCAG AA.
Credits
- Extracted from:
terminal-dreams(src/components/webring/WebringList.tsx). The original was a flat list — one<a>per site with no anchor to a current entry and no prev/next derivation. craft-bits lifts the API into a<nav>landmark with thecurrentflag onWebringSite, computes ring siblings with wrap-around, splits the rendering into a sibling strip and a directory (each independently toggleable), and adds theWebringList.Linkslot so consumers can swap in a framework router viaasChild.