Skip to main content
astrocraft-ui/ components · 101

Progress

Free · MIT

Headless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.

Install

Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).

Unstyled — behavior only, you write the CSS

Install command
npx astrocraft-ui add display/progress

Plain-CSS theme — no build step

Install command
npx astrocraft-ui add display/progress --theme css

Tailwind theme — needs Tailwind v4

Install command
npx astrocraft-ui add display/progress --theme tailwind

Plain-CSS theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add display/progress --theme css --bridge lumos

Tailwind theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add display/progress --theme tailwind --bridge lumos

Live demo

Progress

CircularProgress & Meter

The ring is one dash the length of its own circumference, slid by stroke-dashoffset — no path maths, no clipping element. The meter is the native <meter>: a MEASUREMENT, not a task, and the only element that can say which end of its range is the good one. It ships with no appearance: none, for the same reason the Slider does — zeroing it erases the control in WebKit.

Storage — 7.2 GB of 10 GB7.2 / 10
Score — 8.4 of 108.4 / 10

Attribute surface

With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.

ComponentSlotsVariantsRuntime stateNative state
`CircularProgress.astro``circular-progress` `circular-progress-indicator` `circular-progress-track``data-size`: `sm` · `md` · `lg`——
`Progress.astro``progress` `progress-indicator``data-size`: `sm` · `md` · `lg`——

Source

What the command copies — 4 files, alias-free so the copy lands standing alone. This is the whole component, shown without its comments; the files you install keep them, with the reasoning behind every rule.

src/components/ui/display/progress/CircularProgress.astro
---
import type { HTMLAttributes } from "astro/types";

import { circumference, dashOffset, toPercent } from "./arc";

type Props = HTMLAttributes<"svg"> & {
  value?: number;
  max?: number;
  size?: "sm" | "md" | "lg";
  thickness?: number;
  label?: string;
};

const {
  value = 0,
  max = 100,
  size = "md",
  thickness = 10,
  label,
  class: className,
  ...rest
} = Astro.props;

const pct = toPercent(value, max);
const radius = Math.max(0, 50 - thickness / 2);
const round = (n: number) => Number(n.toFixed(3));
---

<svg
  role="progressbar"
  aria-valuemin={0}
  aria-valuemax={max}
  aria-valuenow={value}
  aria-label={label}
  viewBox="0 0 100 100"
  class={className}
  data-slot="circular-progress"
  data-size={size}
  data-value={round(pct)}
  {...rest}
>
  <circle
    data-slot="circular-progress-track"
    cx="50"
    cy="50"
    r={round(radius)}
    fill="none"
    stroke="currentColor"
    stroke-opacity="0.2"
    stroke-width={thickness}></circle>
  <circle
    data-slot="circular-progress-indicator"
    cx="50"
    cy="50"
    r={round(radius)}
    fill="none"
    stroke="currentColor"
    stroke-width={thickness}
    stroke-linecap="round"
    stroke-dasharray={round(circumference(radius))}
    stroke-dashoffset={round(dashOffset(pct, radius))}
    transform="rotate(-90 50 50)"></circle>
</svg>
src/components/ui/display/progress/Progress.astro
---
import type { HTMLAttributes } from "astro/types";

import { toPercent } from "./arc";

type Props = HTMLAttributes<"div"> & {
  value?: number;
  max?: number;
  size?: "sm" | "md" | "lg";
  label?: string;
};

const { label, value = 0, max = 100, size = "md", class: className, ...rest } = Astro.props;
const pct = toPercent(value, max);
---

<div
  role="progressbar"
  aria-label={label}
  aria-valuemin={0}
  aria-valuemax={max}
  aria-valuenow={value}
  class={className}
  data-slot="progress"
  data-size={size}
  {...rest}
>
  <div style={`width: ${pct}%`} data-slot="progress-indicator"></div>
</div>
src/components/ui/display/progress/arc.ts
export function toPercent(value: number, max: number): number {
  if (!Number.isFinite(value) || !Number.isFinite(max) || max <= 0) return 0;
  return Math.max(0, Math.min(100, (value / max) * 100));
}

export function circumference(radius: number): number {
  return 2 * Math.PI * Math.max(0, radius);
}

export function dashOffset(percent: number, radius: number): number {
  return circumference(radius) * (1 - Math.max(0, Math.min(100, percent)) / 100);
}
src/components/ui/display/progress/index.ts
import CircularProgress from "./CircularProgress.astro";
import Progress from "./Progress.astro";

export { circumference, dashOffset, toPercent } from "./arc";
export { CircularProgress, Progress };
export default Progress;

What you get

The component source, copied into your project by npx astrocraft-ui add display/progress — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.