Interfaces,
reduced.
Useful components.
Thoughtful defaults.
Source you own.
Copy command
One command. One click. Clear feedback when it works, a useful fallback when it doesn’t.
pnpm dlx shadcn@latest add @wlkr/copy-commandpnpm dlx shadcn@latest add @wlkr/copy-commandView source
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
const styles = `
.wlkr-copy-icon{width:16px;height:16px;overflow:visible}
.wlkr-copy-icon g{transform-origin:12px 12px;transition:opacity 180ms ease,transform 180ms ease}
.wlkr-copy-icon g[data-visible=false]{opacity:0;transform:scale(.65) rotate(-12deg)}
.wlkr-copy-icon g[data-visible=true]{opacity:1;transform:scale(1) rotate(0)}
.wlkr-copy-success{color:var(--success,#087d3d)}
.dark .wlkr-copy-success{color:var(--success,#33f282)}
.wlkr-copy-error{color:var(--destructive,#b42318)}
@media(prefers-reduced-motion:reduce){.wlkr-copy-icon g{transition:none}}
`
/** A fixed-slot copy control. Feedback is visual in the icon and announced off-screen. */
export function CopyCommand({ command, className }: { command: string; className?: string }) {
const [state, setState] = React.useState<"idle" | "copying" | "copied" | "error">("idle")
const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null)
const mounted = React.useRef(true)
const locked = React.useRef(false)
React.useEffect(() => { mounted.current = true; return () => { mounted.current = false; if (timer.current) clearTimeout(timer.current) } }, [])
async function copy() {
if (locked.current) return
locked.current = true
if (timer.current) clearTimeout(timer.current)
setState("copying")
try {
await navigator.clipboard.writeText(command)
if (mounted.current) {
setState("copied")
timer.current = setTimeout(() => setState("idle"), 2200)
}
} catch { if (mounted.current) setState("error") }
finally { locked.current = false }
}
return (
<div className={cn("relative flex min-w-0 items-center gap-4", className)}>
<style>{styles}</style>
<code className="min-w-0 flex-1 overflow-x-auto py-3 text-sm whitespace-pre">{command}</code>
<Button type="button" variant="ghost" size="icon" onClick={copy} disabled={state === "copying"} style={{ width: 44, height: 44, flexShrink: 0, padding: 0 }} aria-label={state === "copied" ? "Command copied; copy again" : state === "error" ? "Copy failed; try again" : "Copy command"}>
<svg className="wlkr-copy-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<g data-visible={state === "idle" || state === "copying"}><rect x="8" y="8" width="12" height="12" rx="2" /><path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" /></g>
<g data-visible={state === "copied"} className="wlkr-copy-success"><path d="m5 12 4 4L19 6" /></g>
<g data-visible={state === "error"} className="wlkr-copy-error"><path d="m6 6 12 12M18 6 6 18" /></g>
</svg>
</Button>
<span role="status" className="sr-only">{state === "error" ? "Clipboard unavailable. Select and copy the command, or try again." : state === "copied" ? "Copied to clipboard." : ""}</span>
</div>
)
}
Registry JSONAsync action
An action that follows through. Pending, success, and a second chance when something goes wrong.
pnpm dlx shadcn@latest add @wlkr/async-actionView source
"use client"
import * as React from "react"
import { ArrowUpRight, Check, X } from "lucide-react"
import { Button } from "@/components/ui/button"
const styles = `
.wlkr-action{display:inline-grid!important;grid-template-areas:'state';height:44px;min-height:44px;max-width:100%;padding:0 12px!important}
.wlkr-action-state{grid-area:state;display:inline-flex;align-items:center;justify-content:flex-start;gap:10px;min-height:24px;white-space:normal;text-align:left}
.wlkr-action-state[aria-hidden=true]{visibility:hidden;pointer-events:none}
.wlkr-action-state svg{width:16px;height:16px;flex-shrink:0}
.wlkr-action-success{color:var(--success,#087d3d)}
.dark .wlkr-action-success{color:var(--success,#33f282)}
.wlkr-action-error{color:var(--destructive,#b42318)}
.wlkr-action[aria-busy=true]{opacity:1!important}
.wlkr-action-pending-label{position:relative;display:inline-block}
.wlkr-action-pending-base{opacity:.72}
.wlkr-action-pending-pixels{position:absolute;inset:0;pointer-events:none;mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect width='3' height='3' fill='white'/%3E%3Crect x='4' y='4' width='3' height='3' fill='white'/%3E%3C/svg%3E");mask-size:8px 8px;mask-repeat:repeat}
@keyframes wlkr-action-reconstruct{0%,100%{mask-position:0 0;opacity:.35}50%{mask-position:4px 4px;opacity:1}}
@media(prefers-reduced-motion:no-preference){.wlkr-action-pending-pixels{animation:wlkr-action-reconstruct 2800ms ease-in-out infinite}}
@media(prefers-reduced-motion:reduce){.wlkr-action-pending-base{opacity:1}.wlkr-action-pending-pixels{display:none}}
`
/** One promise at a time. All labels share a reserved slot, so feedback never moves the page. */
export function AsyncAction({ action, children, pendingLabel = "Saving…", successLabel = "Saved", errorLabel = "Could not save. Try again.", failureLabel = "Try again", disabled = false }: {
action: () => Promise<unknown>
children: React.ReactNode
pendingLabel?: string
successLabel?: string
errorLabel?: string
failureLabel?: string
disabled?: boolean
}) {
const [state, setState] = React.useState<"idle" | "pending" | "success" | "error">("idle")
const locked = React.useRef(false)
const mounted = React.useRef(true)
React.useEffect(() => { mounted.current = true; return () => { mounted.current = false } }, [])
async function run() {
if (locked.current) return
locked.current = true
setState("pending")
try { await action(); if (mounted.current) setState("success") }
catch { if (mounted.current) setState("error") }
finally { locked.current = false }
}
return (
<span className="relative inline-flex max-w-full align-middle">
<style>{styles}</style>
<Button className="wlkr-action" type="button" variant="ghost" onClick={run} disabled={disabled || state === "pending"} aria-busy={state === "pending"}>
<span className="wlkr-action-state" aria-hidden={state !== "idle"}><ArrowUpRight aria-hidden="true" />{children}</span>
<span className="wlkr-action-state" aria-hidden={state !== "pending"}><ArrowUpRight aria-hidden="true" style={{visibility:"hidden"}} /><span className="wlkr-action-pending-label"><span className="wlkr-action-pending-base">{pendingLabel}</span><span className="wlkr-action-pending-pixels" aria-hidden="true">{pendingLabel}</span></span></span>
<span className="wlkr-action-state" aria-hidden={state !== "success"}><Check className="wlkr-action-success" aria-hidden="true" />{successLabel}</span>
<span className="wlkr-action-state" aria-hidden={state !== "error"}><X className="wlkr-action-error" aria-hidden="true" />{failureLabel}</span>
</Button>
<span role="status" className="sr-only">{state === "error" ? errorLabel : state === "success" ? successLabel : state === "pending" ? pendingLabel : ""}</span>
</span>
)
}
Registry JSONFilter list
Find the thing you came for. A small, accessible search for lists that don’t need a whole search engine.
3 results
Brand Studio
The source of truth for a considered identity.
WeatherGoblin
A little character in your daily forecast.
PatriBible
Scripture, thoughtfully within reach.
pnpm dlx shadcn@latest add @wlkr/filter-listView source
"use client"
import * as React from "react"
import { Search, X } from "lucide-react"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
export type FilterListItem = { id: string; label: string; description?: string; keywords?: string[] }
/** A small searchable list. Matching ignores case and accents, with a live result count. */
export function FilterList({ items, label = "Search items", placeholder = "Find something…" }: { items: FilterListItem[]; label?: string; placeholder?: string }) {
const [query, setQuery] = React.useState("")
const id = React.useId()
const input = React.useRef<HTMLInputElement>(null)
const normalize = (value: string) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase()
const words = normalize(query).trim().split(/\s+/).filter(Boolean)
const results = items.filter(item => words.every(word => normalize([item.label, item.description, ...(item.keywords ?? [])].join(" ")).includes(word)))
return (
<div className="flex w-full flex-col gap-5">
<style>{`
.wlkr-filter-search{position:relative}
.wlkr-filter-search:focus-within>svg{color:var(--foreground,currentColor)}
.wlkr-filter-search:focus-within::after{content:"";position:absolute;left:0;bottom:1px;width:16px;height:2px;background:var(--foreground,currentColor)}
.wlkr-filter-input[data-slot=input]:focus-visible{outline:none;box-shadow:none;border-color:transparent}
.wlkr-filter-input[data-slot=input]{border:0;box-shadow:none;background:none;border-radius:0}
.wlkr-filter-input{caret-color:var(--foreground,currentColor)}
@supports(caret-shape:block){.wlkr-filter-input{caret-shape:block}}
`}</style>
<div className="wlkr-filter-search flex items-center gap-3">
<Search className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
<label htmlFor={id} className="sr-only">{label}</label>
<Input className="wlkr-filter-input" ref={input} id={id} type="search" placeholder={placeholder} value={query} onChange={event => setQuery(event.target.value)} aria-describedby={`${id}-count`} />
{query && <Button type="button" variant="ghost" size="icon" aria-label="Clear search" onClick={() => { setQuery(""); input.current?.focus() }}><X /></Button>}
</div>
<p id={`${id}-count`} role="status" className="text-xs text-muted-foreground">{results.length} {results.length === 1 ? "result" : "results"}</p>
<ul className="flex flex-col gap-5">
{results.map(item => <li key={item.id}><p className="text-sm font-medium">{item.label}</p>{item.description && <p className="mt-1 text-sm text-muted-foreground">{item.description}</p>}</li>)}
</ul>
{results.length === 0 && <p className="text-sm text-muted-foreground">No matches. Try another search or clear the filter.</p>}
</div>
)
}
Registry JSONPixel object
Give a shape a little life. Project a point cloud or a simple mask into a quiet field of pixels.
pnpm dlx shadcn@latest add @wlkr/pixel-objectView source
"use client"
import * as React from "react"
import { Pause, Play } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
export type PixelPoint = { x: number; y: number; z?: number }
/** A sphere point cloud; pass your own points or a #/space mask to display an object. */
export function spherePoints(count = 420): PixelPoint[] {
const total = Math.max(12, Math.min(2000, Math.round(count)))
return Array.from({ length: total }, (_, index) => {
const y = 1 - (index / (total - 1)) * 2
const radius = Math.sqrt(1 - y * y)
const angle = index * Math.PI * (3 - Math.sqrt(5))
return { x: Math.cos(angle) * radius, y, z: Math.sin(angle) * radius }
})
}
const sphere = spherePoints()
const styles = `
.wlkr-pixel-object{display:flex;flex-direction:column;align-items:center;gap:12px;max-width:100%}
.wlkr-pixel-object svg{display:block;width:100%;height:auto;max-width:320px;overflow:visible}
.wlkr-pixel-toggle{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-width:44px;min-height:44px;padding:0 12px;cursor:pointer}
.wlkr-pixel-toggle svg{width:14px;height:14px}
.wlkr-pixel-toggle:focus-visible{outline:2px solid var(--ring,currentColor);outline-offset:4px}
.wlkr-pixel-note{font-size:12px;color:var(--muted-foreground,#696969)}
`
/**
* A projected pixel object, not a video or canvas. Coordinates are normalized to [-1, 1].
* A mask uses # for filled pixels and spaces/dots for empty pixels; rows need not be equal.
* Motion pauses off-screen, in hidden tabs, on user pause, and for reduced motion.
*/
export function PixelObject({ points, mask, resolution = 36, size = 260, label = "Rotating pixel sphere", rotating = true, className }: {
points?: PixelPoint[]
mask?: string[]
resolution?: number
size?: number
label?: string
rotating?: boolean
className?: string
}) {
const root = React.useRef<HTMLDivElement>(null)
const angle = React.useRef(0)
const [rotation, setRotation] = React.useState(0)
const [paused, setPaused] = React.useState(false)
const [motion, setMotion] = React.useState(false)
const grid = Math.max(8, Math.min(80, Math.round(Number.isFinite(resolution) ? resolution : 36)))
const dimension = Math.max(64, Math.min(1000, Number.isFinite(size) ? size : 260))
const cloud = React.useMemo(() => {
if (mask) {
const rows = mask.slice(0, 80)
const width = Math.max(1, ...rows.map(row => row.slice(0, 80).length))
return rows.flatMap((row, y) => [...row.slice(0, 80)].flatMap((pixel, x) => pixel === "#" ? [{ x: width === 1 ? 0 : (x / (width - 1)) * 2 - 1, y: rows.length === 1 ? 0 : 1 - (y / (rows.length - 1)) * 2, z: 0 }] : []))
}
return (points ?? sphere).filter(point => Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z ?? 0)).slice(0, 2000)
}, [points, mask])
React.useEffect(() => {
const preference = window.matchMedia("(prefers-reduced-motion: reduce)")
let intersecting = false
let frame = 0
let last = 0
let drawn = 0
function stop() { if (frame) cancelAnimationFrame(frame); frame = 0; last = 0 }
function tick(time: number) {
const elapsed = last ? Math.min(time - last, 80) : 0
last = time
angle.current += elapsed * .00015
// Slow rotation sampled at 12 fps keeps the discrete pixels deliberate.
if (time - drawn >= 80) { setRotation(angle.current); drawn = time }
frame = requestAnimationFrame(tick)
}
function sync() {
const active = rotating && !paused && !preference.matches && intersecting && !document.hidden
setMotion(active)
if (active && !frame) frame = requestAnimationFrame(tick)
if (!active) stop()
}
const observer = new IntersectionObserver(entries => { intersecting = entries[0]?.isIntersecting ?? false; sync() }, { threshold: .05 })
if (root.current) observer.observe(root.current)
preference.addEventListener("change", sync)
document.addEventListener("visibilitychange", sync)
sync()
return () => { stop(); observer.disconnect(); preference.removeEventListener("change", sync); document.removeEventListener("visibilitychange", sync) }
}, [rotating, paused])
const pixels = new Map<string, { x: number; y: number; opacity: number }>()
const cosine = Math.cos(rotation)
const sine = Math.sin(rotation)
for (const point of cloud) {
const z = point.z ?? 0
const x = point.x * cosine + z * sine
const depth = -point.x * sine + z * cosine
const column = Math.round((x * .43 + .5) * (grid - 1))
const row = Math.round((-point.y * .43 + .5) * (grid - 1))
if (column < 0 || column >= grid || row < 0 || row >= grid) continue
const opacity = .22 + Math.max(0, Math.min(1, (depth + 1) / 2)) * .73
const key = `${column},${row}`
if (!pixels.has(key) || pixels.get(key)!.opacity < opacity) pixels.set(key, {x:column,y:row,opacity})
}
return <div ref={root} className={cn("wlkr-pixel-object", className)} style={{width:dimension}} data-motion={motion ? "running" : "paused"}>
<style>{styles}</style>
<svg viewBox={`0 0 ${grid} ${grid}`} role="img" aria-label={label}>
{[...pixels.entries()].map(([key, pixel]) => <rect key={key} x={pixel.x} y={pixel.y} width=".72" height=".72" rx=".06" fill="currentColor" opacity={pixel.opacity} />)}
</svg>
{rotating && <Button type="button" className="wlkr-pixel-toggle" variant="ghost" aria-label={paused ? "Resume object rotation" : "Pause object rotation"} aria-pressed={paused} onClick={() => setPaused(value => !value)}>{paused ? <Play aria-hidden="true" /> : <Pause aria-hidden="true" />}<span>{paused ? "Resume" : "Pause"}</span></Button>}
<span role="status" className="sr-only">{motion ? "Object rotation active." : "Object is still."}</span>
</div>
}
Registry JSONPrimitives
Good foundations, straight from shadcn/ui. We use the originals, with WLKR’s shared design tokens.
pnpm dlx shadcn@latest add button input switchInstalled directly from shadcn/ui. Upstream source is unchanged.
Foundations
A common language for what comes next. Semantic color, considered type, and space to think.
Meaning before appearance.
Canvas, foreground, muted, and accent describe a purpose. Their values change with the light. Green marks a useful moment; it never has to carry the message alone.
One typeface. Room to breathe.
Geist. A compact scale. Tight display typography and comfortable reading lengths. Focus stays visible. Motion follows interaction and respects reduced-motion preferences.
pnpm dlx shadcn@latest add @wlkr/themeToken reference
--background Canvas --foreground Primary text --muted-foreground Supporting text --primary Primary action --success Successful completion --destructive Failure feedback --ring Keyboard focus --brand #33F282 --brand-foreground #010101 Apply .dark to the document root for dark mode. The theme leaves your app’s typography unchanged.
Make it yours.
Copy the source into your project with the shadcn CLI. Keep what you need. Change what you like. No component runtime to subscribe to.
1. Start with shadcn
pnpm dlx shadcn@latest initUse an existing React, TypeScript, and Tailwind project. Already configured? Continue below.
2. Add the namespace
Merge this entry into the registries field of your project’s components.json.
"registries": {
"@wlkr": "https://ui.wlkrlabs.com/r/{name}.json"
}3. Bring a component home
pnpm dlx shadcn@latest add @wlkr/copy-commandOriginals install to components/wlkr. Their stock primitives come directly from shadcn. The optional theme adds light and dark color tokens.
Using it at wlkrlabs.com
The current website uses Astro. Future adoption first needs the official @astrojs/react integration and compatible Tailwind/shadcn setup. Import these React components into an Astro page with a client:load directive for interactive islands. Review generated source and token conflicts in a separate website change; the existing site stays unchanged here.
Built with shadcn/ui, Radix, Lucide, and Geist. Based on the official registry starter. WLKR originals are MIT licensed.