Interface
Progress bars with eighth-block precision, in five styles.
Uploading 3 files
1/3 done
Controls
pnpm dlx shadcn@latest add @asciicn/ascii-progressNo engine needed: this one is plain text and CSS. The @asciicn namespace needs one line in components.json, see Installation.
Copy this into components/ascii/ascii-progress.tsx:
import type { ComponentProps } from "react"
import { cn } from "@/lib/utils"
const styles = {
// Eighth blocks give eight steps of precision inside a single character.
blocks: { fill: "█", partial: " ▏▎▍▌▋▊▉", empty: " ", open: "▕", close: "▏" },
shade: { fill: "█", partial: "░▒▓", empty: "░", open: "", close: "" },
hash: { fill: "#", partial: "", empty: ".", open: "[", close: "]" },
arrow: { fill: "=", partial: ">", empty: " ", open: "[", close: "]" },
dots: { fill: "●", partial: "", empty: "○", open: "", close: "" },
} as const
export function progressBar(value: number, width: number, variant: keyof typeof styles = "blocks") {
const s = styles[variant]
const v = Math.min(1, Math.max(0, value)) * width
const full = Math.floor(v)
let bar = s.fill.repeat(full)
if (full < width) {
bar += s.partial ? s.partial[Math.floor((v - full) * s.partial.length)] : s.empty
bar += s.empty.repeat(width - full - 1)
}
return s.open + bar + s.close
}
export type AsciiProgressProps = Omit<ComponentProps<"div">, "children"> & {
/** 0 to 100. */
value: number
/** Bar width in characters. */
width?: number
variant?: keyof typeof styles
label?: string
showValue?: boolean
}
export function AsciiProgress({
value,
width = 24,
variant = "blocks",
label,
showValue = true,
className,
...props
}: AsciiProgressProps) {
const pct = Math.round(Math.min(100, Math.max(0, value)))
return (
<div
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={pct}
aria-label={label}
className={cn("flex items-center gap-[1ch] font-mono whitespace-pre tabular-nums", className)}
{...props}
>
{label && <span className="text-muted-foreground">{label}</span>}
<span aria-hidden>{progressBar(value / 100, width, variant)}</span>
{showValue && <span aria-hidden>{String(pct).padStart(3)}%</span>}
</div>
)
}
import { AsciiProgress } from "@/components/ascii/ascii-progress"
<AsciiProgress value={62} label="upload" />
<AsciiProgress value={62} variant="arrow" width={20} />Five glyph sets. blocks uses eighth-width partials, so a 24-character bar moves in 192 steps.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | — | 0 to 100. |
| width | number | 24 | Bar width in characters. |
| variant | "blocks" | "shade" | "hash" | "arrow" | "dots" | "blocks" | Blocks use eighth-width partials. |
| label | string | — | Shown before the bar and used as the accessible name. |
| showValue | boolean | true | Show the percentage. |
role="progressbar" with aria-valuenow, aria-valuemin and aria-valuemax. The glyphs and the percentage are aria-hidden.tabular-nums.<div>, safe in Server Components.