Aaditya

Pixel Font

Component

About

Bitmap type as SVG over real, selectable text: 5x7 and 8x8 faces, round or square dots, unlit cells and proportional spacing. For status screens, badges and headings.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Pixel Font to your project and fits it to your theme.

Code

How Pixel Font is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client"

import * as React from "react"
import { PixelFont } from "@/components/ui/pixel-font"

const reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches

const SHIFTS = ["Night shift", "Rendering", "Queue clear", "All done"]

const FIT_CSS = "html,body{overflow:hidden}[data-fit-small] [data-fit-hide]{display:none!important}"

/**
 * 21st shows a demo in a small frame: about 674 by 421 on a component page, 358 by 224 on a
 * phone and 245 by 139 on a profile card. A demo taller than its frame gets scrollbars, which
 * render white on dark pages. FitFrame fills the frame exactly instead.
 *
 * - "scale": the content shrinks until it fits, centered. For visuals such as orbs.
 * - "crop": the content stays at full size from the top and fades out at the bottom. For text
 *   that streams in, where a changing scale would make the page breathe.
 *
 * Anything marked `data-fit-hide` (state buttons, the chatcn.dev line) drops out in frames
 * shorter than `hideBelow`, so a profile card shows the visual alone.
 */
function FitFrame({
  children,
  mode = "scale",
  hideBelow = 360,
}: {
  children: React.ReactNode
  mode?: "scale" | "crop"
  hideBelow?: number
}) {
  const content = React.useRef<HTMLDivElement>(null)
  const [cropped, setCropped] = React.useState(false)

  React.useLayoutEffect(() => {
    const el = content.current
    if (!el) return
    const measure = () => {
      const vw = window.innerWidth
      const vh = window.innerHeight
      el.toggleAttribute("data-fit-small", vh < hideBelow)
      // Layout sizes ignore the transform, so measuring never feeds back.
      const w = Math.max(el.offsetWidth, el.scrollWidth)
      const h = Math.max(el.offsetHeight, el.scrollHeight)
      const scale = mode === "scale" ? Math.min(1, vw / w, vh / h) : 1
      el.style.transform = scale < 1 ? `scale(${scale})` : ""
      setCropped(mode === "crop" && h > vh + 1)
    }
    measure()
    const observer = new ResizeObserver(measure)
    observer.observe(el)
    window.addEventListener("resize", measure)
    return () => {
      observer.disconnect()
      window.removeEventListener("resize", measure)
    }
  }, [mode, hideBelow])

  const fade = "linear-gradient(to bottom, #000 calc(100% - 40px), transparent)"
  return (
    <div
      className="flex w-full justify-center overflow-hidden"
      style={{
        height: "100dvh",
        alignItems: mode === "crop" ? "flex-start" : "center",
        maskImage: cropped ? fade : undefined,
        WebkitMaskImage: cropped ? fade : undefined,
      }}
    >
      <style>{FIT_CSS}</style>
      <div
        ref={content}
        className="w-full shrink-0"
        style={{ transformOrigin: mode === "crop" ? "top center" : "center" }}
      >
        {children}
      </div>
    </div>
  )
}

const pad = (n: number) => String(n).padStart(2, "0")

/** The line under each 21st demo that points back to the kit. */
function MoreOn({ slug, children }: { slug: string; children: React.ReactNode }) {
  return (
    <p data-fit-hide className="text-[0.75rem]/[1rem] text-(color:--chatcn-fg-faint) text-center text-balance">
      {children}{" "}
      <a
        href={`https://chatcn.dev/docs/components/${slug}`}
        target="_blank"
        rel="noopener"
        className="text-(color:--chatcn-fg-subtle) hover:text-(color:--chatcn-fg) underline underline-offset-2"
      >
        chatcn.dev
      </a>
    </p>
  )
}

/**
 * A render node's status screen and a headline in bitmap type over real text: the queue drains,
 * the ETA counts down and the headline changes as the night goes on.
 */
export default function PixelFontPreview() {
  const [left, setLeft] = React.useState(42)
  const [shift, setShift] = React.useState(0)
  React.useEffect(() => {
    if (reducedMotion()) return
    const clock = window.setInterval(() => setLeft((s) => (s <= 0 ? 42 : s - 1)), 400)
    const words = window.setInterval(() => setShift((n) => (n + 1) % SHIFTS.length), 1400)
    return () => {
      window.clearInterval(clock)
      window.clearInterval(words)
    }
  }, [])
  const queue = Math.ceil(left / 6)
  const running = shift < 3
  return (
    <FitFrame>
      <div className="flex w-full flex-col items-center justify-center gap-6 p-6">
        <div className="flex w-full max-w-lg flex-col gap-6">
          <PixelFont font="8x8" size={5} proportional uppercase className="text-(color:--chatcn-fg)">
            {SHIFTS[shift] ?? "Night shift"}
          </PixelFont>
          <div className="bg-(color:--chatcn-inset) rounded-(--chatcn-radius-xl) flex flex-col gap-4 p-5">
            <div className="flex items-center justify-between">
              <span className="text-[0.8125rem]/[1.25rem] text-(color:--chatcn-fg-muted)">Render node</span>
              <span
                className={
                  running
                    ? "bg-(color:--chatcn-blue-bg) text-(color:--chatcn-blue) rounded-(--chatcn-radius-sm) px-1.5 py-1"
                    : "bg-(color:--chatcn-green-bg) text-(color:--chatcn-green) rounded-(--chatcn-radius-sm) px-1.5 py-1"
                }
              >
                <PixelFont size={1}>{running ? "RUNNING" : "DONE"}</PixelFont>
              </span>
            </div>
            <PixelFont
              font="8x8"
              size={3}
              dot="round"
              unlit
              className="text-(color:--chatcn-fg)"
              text={`GPU 03  L40S\nQUEUE   ${pad(queue)}\nETA   0:${pad(left)}`}
            />
          </div>
        </div>
        <MoreOn slug="pixel-font">
          Two bitmap faces, round or square dots, real selectable text. On
        </MoreOn>
      </div>
    </FitFrame>
  )
}

More by Aaditya

View all
  • Pixel Orb

    Pixel Orb

    9
  • Dither Orb

    Dither Orb

    9
  • Dot Matrix Loader

    Dot Matrix Loader

    8
  • Halftone Loader

    Halftone Loader

    6
  • ASCII 3D

    ASCII 3D

    5
  • Wireframe Globe

    Wireframe Globe

    5
  • Audio Visualizer

    Audio Visualizer

    5
  • LED Matrix

    LED Matrix

    4