afterglow
componentboot-logsource: ok · hit
Boot Log
A self test that prints itself line by line, at its final height from the first frame.
npx shadcn add "https://afterglow.thebuilder.dk/r/boot-log.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
@afterglow/caret
Files1 · source cached 10/9/2026, 5:25:32 PM
registry/terminal/components/boot-log.tsx
"use client";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { Caret } from "@/registry/terminal/components/caret";
type BootTone = "default" | "dim" | "ok" | "warn" | "error";
type BootLine = string | { text: string; tone?: BootTone };
const TONE_CLASS: Record<BootTone, string> = {
default: "text-phosphor-bright",
dim: "text-phosphor-dim",
error: "text-signal",
ok: "text-phosphor",
warn: "text-warning",
};
function toLine(line: BootLine): { text: string; tone: BootTone } {
return typeof line === "string"
? { text: line, tone: "default" }
: { text: line.text, tone: line.tone ?? "default" };
}
function BootLog({
className,
lines,
interval = 240,
prefix = ">",
onComplete,
...props
}: Omit<React.ComponentProps<"ol">, "children"> & {
lines: BootLine[];
interval?: number;
prefix?: string;
onComplete?: () => void;
}) {
const [printed, setPrinted] = useState(0);
const total = lines.length;
const done = printed >= total;
const finished = useRef(onComplete);
useEffect(() => {
finished.current = onComplete;
}, [onComplete]);
useEffect(() => {
if (done) {
return;
}
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
if (reduced.matches) {
setPrinted(total);
return;
}
const timer = window.setTimeout(() => setPrinted(printed + 1), interval);
return () => window.clearTimeout(timer);
}, [printed, total, interval, done]);
useEffect(() => {
if (done) {
finished.current?.();
}
}, [done]);
return (
<ol
className={cn(
"m-0 grid list-none gap-1 p-0 font-mono text-xs",
className
)}
data-slot="boot-log"
{...props}
>
{lines.map(toLine).map((line, index) => {
const shown = index < printed;
return (
<li
aria-hidden={shown ? undefined : "true"}
className={cn(
"flex gap-2 leading-relaxed",
TONE_CLASS[line.tone],
shown ? "animate-type" : "invisible"
)}
key={line.text}
>
<span className="shrink-0 text-phosphor-dim">{prefix}</span>
<span className="min-w-0">{line.text}</span>
{shown && index === printed - 1 && printed < total && (
<Caret className="self-center" />
)}
</li>
);
})}
</ol>
);
}
export type { BootLine, BootTone };
export { BootLog };