bottega

backgrounds

Concentric Rings

A server component that renders a tileable repeating-radial-gradient ring pattern across the full surface. Zero JS — ring color derives from --foreground at 14% opacity (reads on both themes); field derives from --background. Place in a relative parent; the layer fills it absolutely with pointer-events-none.

Ported from Javierrocadev (MIT)

concentric-rings-bg

Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/concentric-rings-bg

Usage

Usagetsx
<ConcentricRingsBg />

Props

PropTypeDefaultDescription
sizenumber50Cell size in pixels (diameter of each ring tile). Default: 50.
ringWidthnumber2Ring stop width as a percentage of the cell radius (1–10). Default: 2.
fadebooleanfalseApply a radial fade-out mask toward edges. Default: false.

Source

concentric-rings-bg.tsxtsx
import type React from "react";

export interface ConcentricRingsBgProps extends React.HTMLAttributes<HTMLDivElement> {
  /** Cell size in pixels (diameter of each ring tile). Default: 50. */
  size?: number;
  /** Ring stop width as a percentage of the cell radius (1–10). Default: 2. */
  ringWidth?: number;
  /** Apply a radial fade-out mask toward edges. Default: false. */
  fade?: boolean;
}

/**
 * Tileable concentric-rings background layer (server component, zero JS).
 * Reproduces the repeating-radial-gradient ring pattern from uiverse.io/Javierrocadev.
 * Ring color derives from --foreground at low opacity; field derives from --background.
 * Place in a `relative` parent — fills the parent absolutely with pointer-events-none.
 */
export function ConcentricRingsBg({
  size = 50,
  ringWidth = 2,
  fade = false,
  className = "",
  style,
  ...props
}: ConcentricRingsBgProps) {
  // Ring appears at the very edge of each radial tile.
  // ringStart% = 100 - ringWidth*2 keeps a thin band at ~90% of the radius (matching original 88%→90%).
  const ringStart = Math.max(0, 100 - ringWidth * 2);
  const ringEnd = Math.min(100, ringStart + ringWidth);

  // Field = var(--background); ring ink = foreground at 14% opacity so it reads on both themes.
  const bg = `repeating-radial-gradient(
    var(--background) ${ringStart}%,
    color-mix(in oklch, var(--foreground) 14%, transparent) ${ringEnd}%
  )`;

  return (
    <div
      aria-hidden="true"
      className={["absolute inset-0 pointer-events-none", className]
        .filter(Boolean)
        .join(" ")}
      style={{
        backgroundImage: bg,
        backgroundSize: `${size}px ${size}px`,
        backgroundColor: "var(--background)",
        ...(fade
          ? {
              /* mask alpha — not a UI color */
              maskImage:
                "radial-gradient(ellipse 80% 80% at 50% 50%, rgb(0 0 0 / 1) 40%, transparent 100%)",
              WebkitMaskImage:
                "radial-gradient(ellipse 80% 80% at 50% 50%, rgb(0 0 0 / 1) 40%, transparent 100%)",
            }
          : {}),
        ...style,
      }}
      {...props}
    />
  );
}

Dependencies

  • @bottega/tokens