← ui

shade

lib

a colour effect kept apart from what it colours: solid, aurora, or your own

npx shadcn add @ag/shade
Already installed? Preview an update
npx shadcn add @ag/shade --dry-run

Your installed source stays unchanged until you update it. How updates work

also called gradient

shelf foundations

used by aurora, foil

the demo above, verbatim

shade.demo.tsx
"use client"

import type * as React from "react"
import { Sample } from "@/app/samples"
import { aurora } from "@/registry/base-nova/lib/aurora"
import { type Shade, shadeTable, solid } from "@/registry/base-nova/lib/shade"

// #region shade
/** One base colour, one travel (x 0 to 1, left to right), two shades: solid keeps the
 *  base all the way, aurora travels its palette. A drawing that takes a shade wears
 *  whichever it is handed. The strip is one gradient through the travel's colours,
 *  blended in OKLCH, the way the light reads once drawn, not a row of samples. */
const BASE = { css: "oklch(0.87 0.19 150)", hue: 150 }
const STEPS = 24

export function Travel({
  shade,
  base = BASE,
}: {
  shade: Shade
  base?: typeof BASE
}) {
  const stops = shadeTable(shade, base.css, base.hue, STEPS).join(", ")
  return (
    <div
      className="h-6 rounded-md bg-(image:--travel)"
      style={
        {
          "--travel": `linear-gradient(to right in oklch, ${stops})`,
        } as React.CSSProperties
      }
    />
  )
}
// #endregion

export default function ShadeDemo() {
  return (
    <Sample
      name="shade"
      glance
      title="one travel, two shades"
      description="solid above, aurora below"
      with="shade"
    >
      <div className="dark space-y-2 rounded-xl bg-black p-5 text-foreground">
        <Travel shade={solid} />
        <Travel shade={aurora()} />
      </div>
    </Sample>
  )
}

the source, every file an install writes

shade.ts
// SHADE: a colour effect, apart from what is drawn with it. A drawing knows how far
// along a thing is (`x`, 0..1: a peak's energy, the age of a trail, the top of a glow)
// and has one base colour (a person's, the text's); a shade says what colour that is
// there. The same drawing in `solid` is one colour, pure light; in lib/aurora's
// `aurora` it travels an aurora borealis's palette. Any other effect is one more
// Shade.
//
// A shade's colours are CSS (relative colours welcome: `oklch(from <base> …)`), the
// same in a stylesheet, an inline style or a canvas; `shadeTable` makes a travel's
// steps once, for a canvas to resolve through `getComputedStyle`.

export interface Shade {
  /** Its identity: two shades with one name colour the same (a drawing redoes its
   *  colours only when this changes). */
  name: string
  /** The colour `x` (0..1) along the travel from `base` (any CSS colour), whose OKLCH
   *  hue is `baseHue`. */
  color(base: string, baseHue: number, x: number): string
}

/** One colour all the way: the base itself, light without a palette. */
export const solid: Shade = { name: "solid", color: (base) => base }

/** `steps` colours along the travel, from x = 0 to x = 1. */
export function shadeTable(
  shade: Shade,
  base: string,
  baseHue: number,
  steps: number,
): string[] {
  if (!(Number.isInteger(steps) && steps >= 2))
    throw new Error(
      `shade ${shade.name}: ${steps} steps; a travel needs at least 2`,
    )
  return Array.from({ length: steps }, (_, k) =>
    shade.color(base, baseHue, k / (steps - 1)),
  )
}