Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 36 additions & 51 deletions .cursor/skills/23rd/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
name: 23rd
description: >
Install, choose, and compose 23rd shadcn/ui registry components for React and
Svelte 5. Use when the user wants a UI component, background, shader, WebGL
Install, choose, and compose 23rd shadcn/ui registry components for React.
Use when the user wants a UI component, background, shader, WebGL
wash, liquid metal or chrome, hero atmosphere, footer, 404, color picker, typewriter text, keyboard shortcut recorder, ASCII effect,
starfield, orb, or page-tilt from the 23rd registry (@23rd, 23rd.dev, radiumcoders/23rd.dev),
or when adding a new component to this registry. Covers install commands,
Expand All @@ -14,7 +14,7 @@ metadata:

# 23rd

23rd is a shadcn registry of opinionated components. Install with the shadcn CLI, then own the source. Every published item has a React build and a Svelte 5 build. This skill is the playbook. Open the linked files before inventing props.
23rd is a shadcn registry of opinionated components. Install with the shadcn CLI, then own the source. Every published item is a single React client component. This skill is the playbook. Open the linked files before inventing props.

This folder is checked in twice and the copies must stay identical. `skills/23rd/` is the path the skills CLI and [skills.sh](https://skills.sh) discover. `.cursor/skills/23rd/` is what Cursor loads as `/23rd` in this repo.

Expand All @@ -29,33 +29,21 @@ Do not invent props, CSS variables, or variants. If a prop is not in [apis.md](a

## Install

The consumer project must already be a shadcn project. React files import `cn` from `@/lib/utils` and land in `components/ui/`. Svelte files inline their own `cn`, use `class` (not `className`), and land in `src/lib/components/ui/`. React components are `"use client"`.
The consumer project must already be a shadcn project. React files import `cn` from `@/lib/utils` and land in `components/ui/`. React components are `"use client"`.

Replace `<name>` with the kebab-case item (`shader-gradient`, `gooey-color-picker`, …).

React:

```bash
pnpm dlx shadcn@latest add @23rd/<name>
npx shadcn@latest add @23rd/<name>
yarn dlx shadcn@latest add @23rd/<name>
bunx --bun shadcn@latest add @23rd/<name>
```

Svelte 5 (item suffix `-svelte`):

```bash
pnpm dlx shadcn@latest add @23rd/<name>-svelte
npx shadcn@latest add @23rd/<name>-svelte
yarn dlx shadcn@latest add @23rd/<name>-svelte
bunx --bun shadcn@latest add @23rd/<name>-svelte
```

GitHub source, if `@23rd` is not configured:

```bash
pnpm dlx shadcn@latest add radiumcoders/23rd.dev/<name>
pnpm dlx shadcn@latest add radiumcoders/23rd.dev/<name>-svelte
```

Imports after install:
Expand All @@ -64,20 +52,14 @@ Imports after install:
import { ShaderGradient } from "@/components/ui/shader-gradient"
```

```svelte
<script>
import ShaderGradient from "$lib/components/ui/shader-gradient.svelte"
</script>
```

npm dependencies the registry declares:

| Item | React | Svelte |
| --- | --- | --- |
| `gooey-color-picker` | `motion` | none |
| `ring-tower` | `three` | `three` |
| `mech-keyboard` | `three` | `three` |
| every other item | none | none |
| Item | Dependencies |
| --- | --- |
| `gooey-color-picker` | `motion` |
| `ring-tower` | `three` |
| `mech-keyboard` | `three` |
| every other item | none |

Published index: `https://23rd.dev/r/registry.json`. Docs: `https://23rd.dev/docs`.

Expand All @@ -90,6 +72,7 @@ flowchart TD
pick -->|Pick or edit a color| gooey[gooey-color-picker]
pick -->|Atmosphere behind a hero| atmo{Mood}
pick -->|Footer or page-end| foot{Treatment}
pick -->|Reveal an image from a dither| ds[dither-scan]
pick -->|Full page 404| d404[dithered-404]
pick -->|Scroll makes the page lean| folio[folio]
pick -->|Character or wordmark| mark{Which}
Expand Down Expand Up @@ -118,6 +101,7 @@ Disambiguation that agents get wrong:
| Soft gradient behind a headline | `shader-gradient` | `gooey-color-picker` |
| Fire, heat under a hero | `shader-anime-fire` | `dithered-404` unless the page is a 404 |
| 404 that burns under the cursor | `dithered-404` | `shader-anime-fire` |
| Image that un-dithers, scan line, enhance or processing reveal | `dither-scan` | `dithered-404` (a 404 glyph, not your image) |
| Overscroll rubber band, aurora at the bottom | `stretchy-footer` | `folio` |
| Page leans while scrolling | `folio` | `stretchy-footer` (that one stretches, it does not tilt) |
| Stars that speed up when you scroll | `radiant-lines` | `logo-burst` |
Expand Down Expand Up @@ -148,7 +132,7 @@ Same shell for `ShaderAnimeFire`, `ShaderFire`, `ShaderSky`, `AsciiFluid`, `Logo

`ShaderMetal` uses the same shell but is transparent: the ribbons and their shadows sit on the parent's background, so set one (`bg-background`). The ribbons cross the middle; put copy top-left.

`RadiantLines` is transparent and warps with scroll. Inside an overflow div, pass the scroller (`containerRef` in React, `container` in Svelte) and make the canvas `sticky top-0 h-svh`. Omit the scroller to use the window.
`RadiantLines` is transparent and warps with scroll. Inside an overflow div, pass the scroller (`containerRef`) and make the canvas `sticky top-0 h-svh`. Omit the scroller to use the window.

`Folio` and `StretchyFooter` are the scroller by default. Put the page in `children`. For a real document, set `windowScroll` and mark the tilting or lifting element (`data-folio-page` or `data-stretchy-page`).

Expand Down Expand Up @@ -180,26 +164,27 @@ Exceptions:

Categories match `content/docs/components/meta.json`.

| Name | Category | One line | Frameworks |
| --- | --- | --- | --- |
| `logo-burst` | Background | Hair-line tentacles explode from center, then breathe | React + Svelte |
| `phosphor-score` | Background | Vertical CRT score; notes fall, bloom, flare | React + Svelte |
| `radiant-lines` | Background | Hyperspace streaks; warp follows scroll | React + Svelte |
| `ascii-fluid` | Background | Pointer trails quantized to an ASCII brightness ramp | React + Svelte |
| `iso-lattice` | Background | Isometric tiles build solid figures from the floor up | React + Svelte |
| `ring-tower` | Background | three.js tower of tilted image bands; drag to spin, neighbors counter-turn | React + Svelte |
| `shader-gradient` | Shaders | Quiet WebGL wash behind heroes and empty states | React + Svelte |
| `shader-anime-fire` | Shaders | Cel-shaded flames licking up from the bottom | React + Svelte |
| `shader-sky` | Shaders | Clear sky or rain; optional dotted window glass | React + Svelte |
| `shader-metal` | Shaders | Chrome, gold, copper, or graphite ribbons; pointer key light | React + Svelte |
| `stretchy-footer` | Footers | Dia-style rubber overscroll with an aurora floor | React + Svelte |
| `live-orb` | Characters | Lit sphere; eyes follow the pointer | React + Svelte |
| `ascii-logo` | Characters | ASCII wordmark: hover shove, click scatter / fall / gather | React + Svelte |
| `dithered-404` | Pages | Bayer 404 burned by a fireball cursor, then reforms | React + Svelte |
| `folio` | Sections | Page leans on scroll, then springs flat | React + Svelte |
| `gooey-color-picker` | Components | Swatch opens into hue, alpha, and hex under an SVG goo filter | React + Svelte |
| `typewriter` | Components | A real typewriter in SVG: keys press, typebars strike, the carriage steps | React + Svelte |
| `mech-keyboard` | Components | Isometric 75% keyboard illustration; presses with yours and records shortcuts | React + Svelte |
| Name | Category | One line |
| --- | --- | --- |
| `dither-scan` | AI Components | 1-bit dithered image a diagonal scan line clears in N passes |
| `logo-burst` | Background | Hair-line tentacles explode from center, then breathe |
| `phosphor-score` | Background | Vertical CRT score; notes fall, bloom, flare |
| `radiant-lines` | Background | Hyperspace streaks; warp follows scroll |
| `ascii-fluid` | Background | Pointer trails quantized to an ASCII brightness ramp |
| `iso-lattice` | Background | Isometric tiles build solid figures from the floor up |
| `ring-tower` | Background | three.js tower of tilted image bands; drag to spin, neighbors counter-turn |
| `shader-gradient` | Shaders | Quiet WebGL wash behind heroes and empty states |
| `shader-anime-fire` | Shaders | Cel-shaded flames licking up from the bottom |
| `shader-sky` | Shaders | Clear sky or rain; optional dotted window glass |
| `shader-metal` | Shaders | Chrome, gold, copper, or graphite ribbons; pointer key light |
| `stretchy-footer` | Footers | Dia-style rubber overscroll with an aurora floor |
| `live-orb` | Characters | Lit sphere; eyes follow the pointer |
| `ascii-logo` | Characters | ASCII wordmark: hover shove, click scatter / fall / gather |
| `dithered-404` | Pages | Bayer 404 burned by a fireball cursor, then reforms |
| `folio` | Sections | Page leans on scroll, then springs flat |
| `gooey-color-picker` | Components | Swatch opens into hue, alpha, and hex under an SVG goo filter |
| `typewriter` | Components | A real typewriter in SVG: keys press, typebars strike, the carriage steps |
| `mech-keyboard` | Components | Isometric 75% keyboard illustration; presses with yours and records shortcuts |

## Minimal installs that must be right

Expand Down Expand Up @@ -249,8 +234,8 @@ export function Accent() {
}
```

Svelte equivalents use `class`, default imports, and `@23rd/<name>-svelte`. Full prop tables are in [apis.md](apis.md). More assemblies are in [recipes.md](recipes.md).
Full prop tables are in [apis.md](apis.md). More assemblies are in [recipes.md](recipes.md).

## Adding a component

Only when the task is to add one to this repository. Follow [conventions.md](conventions.md). Short version: `registry/<name>/` with vanilla engine plus React and Svelte wrappers and a two-item `registry.json`, a doc at `content/docs/components/<name>.mdx`, a row in `content/docs/components/meta.json`, then `pnpm registry:build` and `pnpm test`.
Only when the task is to add one to this repository. Follow [conventions.md](conventions.md). Short version: `registry/<name>/` with a vanilla engine, a React wrapper, and a `registry.json`, a doc at `content/docs/components/<name>.mdx`, a row in `content/docs/components/meta.json`, then `pnpm registry:build` and `pnpm test`.
41 changes: 32 additions & 9 deletions .cursor/skills/23rd/apis.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
# Public APIs

Types below are what the React modules export. Svelte components take the same fields with these renames:

| React | Svelte |
| --- | --- |
| `className` | `class` |
| `children?: ReactNode` | `children?: Snippet` |
| `scrollRef?: RefObject<HTMLElement \| null>` | `scrollEl?: HTMLElement` |
| `containerRef?: RefObject<HTMLElement \| null>` | `container?: HTMLElement` |
Types below are what the React modules export.

Vanilla `onThemeChange` and `onHasGl` are not React props. The wrappers subscribe internally. `Dithered404` vanilla `onHideCursor` is also internal to the wrapper.

Expand Down Expand Up @@ -389,6 +382,36 @@ type Dithered404Props = {

`LIGHT_COLOR` `#18181B`, `DARK_COLOR` `#E4E4E7`. `dither={false}` is soft fire on the same glyph. No `text` prop. Wrapper does not expose `onHideCursor`.

## dither-scan

```ts
type DitherScanProps = {
src: string
alt?: string
passes?: number // 4, clamped 1–12
duration?: number // 1400 ms per pass
pause?: number // 280 ms between passes
angle?: number // 45, degrees the line travels (0 = left to right)
alternate?: boolean // true, odd passes run back
pixelSize?: number // 6 CSS px, first-pass cell
blur?: number // 6 CSS px trailing the line, 0 off
colors?: readonly [shadow: string, highlight: string] // hex
lineColor?: string // default inverts what is under the line
fit?: "cover" | "contain" // "cover"
autoPlay?: boolean // true, starts when 35% in view
loop?: boolean // false
loopDelay?: number // 2400 ms on the clean image
theme?: "light" | "dark" | "auto"
replayKey?: number // increment to re-dither and scan
onStart?: () => void
onPass?: (pass: number, passes: number) => void
onComplete?: () => void
className?: string
}
```

`LIGHT_COLORS` `["#18181B", "#F4F4F5"]`, `DARK_COLORS` `["#09090B", "#D4D4D8"]`. Also exports `levelSpec(level, passes, pixelSize)`. Vanilla `onLoad` and `onScanningChange` are internal to the wrapper, which sets `data-scanning` and `aria-busy` while sweeping.

## folio

```ts
Expand All @@ -415,7 +438,7 @@ type FolioPlayDetail = {
}
```

React exports `playFolioDemo`, `FOLIO_PLAY` (`"folio:play"`), `applyFolioFrame`. The Svelte component listens for that window event. Reduced motion: no tilt, no blur.
React exports `playFolioDemo`, `FOLIO_PLAY` (`"folio:play"`), `applyFolioFrame`. Reduced motion: no tilt, no blur.

## typewriter

Expand Down
40 changes: 34 additions & 6 deletions .cursor/skills/23rd/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Component guide

React snippets are the primary examples. Svelte parity: same props, `class` instead of `className`, default import from `$lib/components/ui/<name>.svelte`, `children` is a snippet, `scrollRef` is `scrollEl`, `containerRef` is `container`. Read [apis.md](apis.md) before adding a prop.
Read [apis.md](apis.md) before adding a prop.

Shared shell for fill-the-parent pieces:

Expand Down Expand Up @@ -46,7 +46,7 @@ Best fit: an infrastructure or developer-tool hero that wants one luminous objec

Not this: hair-line explosion (`logo-burst`), scroll starfield (`radiant-lines`), images on a tower (`ring-tower`).

Deps: none (WebGL). Key props: `shapes` (`["cube", "steps", "pillars", "coil"]`), `interval` (4s), `color`, `theme` (`auto`), `size` (0.6), `spacing` (8px), `glow` (0.6), `speed` (1), `floor` (true), `interactive` (true).
Deps: none (WebGL). Key props: `shapes` (`["cube", "steps", "pillars", "coil"]`), `interval` (4s), `color`, `theme` (`auto`), `size` (0.6), `spacing` (8px), `speed` (1), `floor` (true), `interactive` (true).

```tsx
"use client"
Expand Down Expand Up @@ -99,7 +99,7 @@ Best fit: a long scrolling page that should feel like hyperspace. Not a static p

Not this: `logo-burst` (one-shot explosion), `shader-gradient` (no scroll coupling).

Deps: none. Key props: `colors` (omit it and the palette follows the theme), `theme`, `starCount` (420), `displacement` (1), `containerRef` / Svelte `container`.
Deps: none. Key props: `colors` (omit it and the palette follows the theme), `theme`, `starCount` (420), `displacement` (1), `containerRef`.

```tsx
"use client"
Expand Down Expand Up @@ -282,7 +282,7 @@ Best fit: a Dia-like end of a long page. The component can be the scroller, or a

Not this: tilting the page (`folio`).

Deps: none. Key props: `children`, `scrollRef` / Svelte `scrollEl`, `windowScroll` (false), `contentSelector` (`[data-stretchy-page]`), `maxStretch` (280), `colors`, `stiffness` (380), `damping` (32), `columns` (9), `blur` (16), `glow` (0), `flip` (false), `rotate` (0 or 180), `label`, `demoId`.
Deps: none. Key props: `children`, `scrollRef`, `windowScroll` (false), `contentSelector` (`[data-stretchy-page]`), `maxStretch` (280), `colors`, `stiffness` (380), `damping` (32), `columns` (9), `blur` (16), `glow` (0), `flip` (false), `rotate` (0 or 180), `label`, `demoId`.

```tsx
"use client"
Expand Down Expand Up @@ -382,6 +382,34 @@ export function NotFound() {

Pitfalls: `interactive` hides the system cursor while the fireball is active (`onHideCursor` exists on the vanilla options; the React wrapper owns that state and does not expose the callback as a prop). Ink follows theme: `#18181B` light, `#E4E4E7` dark. The glyph string is the 404 itself; there is no `text` prop.

## dither-scan

AI component. An image that starts as a 1-bit ordered dither in theme ink and paper. A diagonal scan line sweeps it `passes` times; each pass shrinks the cells and brings back tones and color, and the last leaves the source image.

Best fit: a cover image, product shot, or case-study hero that should feel processed or "enhanced" as it arrives. One per view.

Not this: a 404 page (`dithered-404`), a background wash (`shader-gradient`), or an image gallery. It plays once per scroll into view unless `loop` is set.

Deps: none. Key props: `src`, `alt`, `passes` (4), `duration` (1400), `angle` (45), `alternate` (true), `pixelSize` (6), `blur` (6), `autoPlay` (true), `loop` (false), `replayKey`, `onPass`, `onComplete`.

```tsx
"use client"
import { DitherScan } from "@/components/ui/dither-scan"

export function Cover() {
return (
<DitherScan
src="/cover.jpg"
alt="The studio at dusk"
passes={5}
className="mx-auto max-w-2xl rounded-xl"
/>
)
}
```

Pitfalls: remote images must send CORS headers; an unreadable image is shown clean with no scan. The frame takes the image's aspect ratio and full width; set height and width in `className` to crop instead (`fit` picks cover or contain). Reduced motion shows the clean image immediately.

## folio

Section. The sheet tips one way on scroll down, the other on scroll up, blur on the traveling edge, then springs flat. Peak tilt is an internal 16°.
Expand Down Expand Up @@ -417,7 +445,7 @@ Best fit: pick or edit one color inline.

Not this: any background, shader, or theme switcher.

Deps: React needs `motion`. Svelte has none. Props: `value`, `defaultValue`, `onChange(color, css)`, `label` (`"Color picker"`), `className` / `class`.
Deps: `motion`. Props: `value`, `defaultValue`, `onChange(color, css)`, `label` (`"Color picker"`), `className`.

```tsx
"use client"
Expand All @@ -437,7 +465,7 @@ Controlled when `value` is not `undefined`. Otherwise uncontrolled, seeded from

`GooeyColor` is `{ h: 0–360, s: 0–100, l: 0–100, a: 0–1 }`. Strings accept hex (`#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`) and `hsl` / `hsla`. `onChange` second arg is `hsla(...)` from `toCss`. EyeDropper appears only when `window.EyeDropper` exists; it is not a prop.

Pitfalls: the panel overflows the trigger. Do not put it in `overflow-hidden`. React also exports `parseColor`. No `className` on Svelte; use `class`.
Pitfalls: the panel overflows the trigger. Do not put it in `overflow-hidden`. Also exports `parseColor`.

## mech-keyboard

Expand Down
Loading
Loading