Snippets.

React components, hooks, helpers and the odd shell life hack. Components come with a preview; everything else is code you can take straight out.

12 snippets
01Component

Button

Two variants, pill geometry, and a className that can still override the base. No variant library needed.

Save changesCancelDisabled
import { cn } from '@/lib/cn'

type Props = React.ComponentProps<'button'> & {
  variant?: 'solid' | 'ghost'
}

export function Button({ variant = 'solid', className, ...rest }: Props) {
  return (
    <button
      className={cn(
        'inline-flex items-center gap-2 rounded-full',
        'px-5 py-3 text-sm font-medium transition-colors',
        'disabled:pointer-events-none disabled:opacity-50',
        variant === 'solid' && 'bg-stone-900 text-stone-50',
        variant === 'ghost' && 'border border-stone-300',
        className,
      )}
      {...rest}
    />
  )
}
ui/button.tsx
03Component

Segmented control

Real radio inputs under the pills, so arrow keys work and screen readers announce it as one group.

OverviewActivitySettings
export function Segmented({ options, name, value, onChange }) {
  return (
    <div role="radiogroup"
         className="inline-flex rounded-full bg-stone-200 p-1">
      {options.map((o) => (
        <label key={o}
          className="cursor-pointer rounded-full px-4 py-2
                     text-sm has-[:checked]:bg-white">
          <input type="radio" name={name} value={o}
                 checked={value === o}
                 onChange={() => onChange(o)}
                 className="sr-only" />
          {o}
        </label>
      ))}
    </div>
  )
}
ui/segmented.tsx
05Hook · Motion

useReveal — enter on scroll

One observer, unobserved after the first hit. This is the rise-and-fade used across this site.

import { useEffect, useRef, useState } from 'react'

export function useReveal<T extends HTMLElement>() {
  const ref = useRef<T>(null)
  const [shown, setShown] = useState(false)

  useEffect(() => {
    const el = ref.current
    if (!el) return
    const io = new IntersectionObserver(([e]) => {
      if (!e.isIntersecting) return
      setShown(true)
      io.unobserve(el)
    }, { rootMargin: '-12% 0px' })
    io.observe(el)
    return () => io.disconnect()
  }, [])

  return { ref, shown }
}
use-reveal.ts
07Component

CopyButton

The control on every card here. The confirmation state is the whole point — aria-live announces it too.

CopyCopied ✓
'use client'
import { useState } from 'react'

export function CopyButton({ value }: { value: string }) {
  const [copied, setCopied] = useState(false)

  async function copy() {
    await navigator.clipboard.writeText(value)
    setCopied(true)
    setTimeout(() => setCopied(false), 1600)
  }

  return (
    <button onClick={copy} aria-live="polite">
      {copied ? 'Copied ✓' : 'Copy'}
    </button>
  )
}
ui/copy-button.tsx
09Component

Skeleton

Match the shape of the content it replaces, not a generic grey box, and let it hold still for anyone who asked for less motion.

import { cn } from '@/lib/cn'

export function Skeleton({ className }: { className?: string }) {
  return (
    <div
      aria-hidden
      className={cn(
        'animate-pulse rounded bg-stone-200',
        'motion-reduce:animate-none',
        className,
      )}
    />
  )
}

// <Skeleton className="h-3 w-4/5" />
ui/skeleton.tsx
11Utility · TypeScript

assertNever

Add a variant to the union and the compiler points at every switch that forgot it. Cheapest safety net in the language.

export function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`)
}

type Status = 'queued' | 'running' | 'failed'

function label(s: Status) {
  switch (s) {
    case 'queued':  return 'Waiting'
    case 'running': return 'In progress'
    case 'failed':  return 'Failed'
    default: return assertNever(s)
  }
}
lib/assert-never.ts
02Hook

useMediaQuery

Subscribes through useSyncExternalStore, so it never flashes the wrong branch during hydration.

import { useCallback, useSyncExternalStore } from 'react'

export function useMediaQuery(query: string) {
  const subscribe = useCallback((notify) => {
    const mql = window.matchMedia(query)
    mql.addEventListener('change', notify)
    return () => mql.removeEventListener('change', notify)
  }, [query])

  return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(query).matches,
    () => false, // server snapshot
  )
}
use-media-query.ts
04Utility

cn() — class merge

Conditional classes that also resolve Tailwind conflicts, so a prop can override a base class instead of fighting it.

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
lib/cn.ts
06Shell · Docker

Reclaim a full disk on a box

What I run when a small EC2 instance fills up with old layers. Look before you prune — the last line deletes.

# where did it all go?
docker system df -v | head -20
du -sh /var/lib/docker/* | sort -h

# dangling images and stopped containers only
docker image prune -f
docker container prune -f

# everything not used by a running container
# (read that again before you run it)
docker system prune -af --volumes
disk-full.sh
08Hook

useDebouncedValue

Debounce the value, not the handler. Feed it straight into a query key so typing does not fire a request per keystroke.

import { useEffect, useState } from 'react'

export function useDebouncedValue<T>(value: T, ms = 300) {
  const [debounced, setDebounced] = useState(value)

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), ms)
    return () => clearTimeout(id)
  }, [value, ms])

  return debounced
}

// const q = useDebouncedValue(input, 250)
// useQuery({ queryKey: ['search', q], ... })
use-debounced-value.ts
10Utility · TanStack Query

Query option factories

Keys and fetchers defined once. Prefetch on the server and read on the client without the key drifting between them.

import { queryOptions } from '@tanstack/react-query'

export const eventQueries = {
  all: () => ['events'] as const,
  detail: (id: string) =>
    queryOptions({
      queryKey: [...eventQueries.all(), id],
      queryFn: () => fetchEvent(id),
      staleTime: 60_000,
    }),
}

// server: await qc.prefetchQuery(eventQueries.detail(id))
// client: useSuspenseQuery(eventQueries.detail(id))
queries/events.ts
12CSS

Three lines of type polish

Balanced headlines, no orphan lines in body copy, and motion that switches itself off for people who asked.

h1, h2, h3 { text-wrap: balance; }
p, li      { text-wrap: pretty; }

/* never ship an animation without this */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
globals.css

Have something to ship?Let’s talk.

© 2026 Elian Richard
Elian Richard