From 0177f23a1ed5811e8f219eac5222c6fbe3721a5e Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 19:40:33 +0530 Subject: [PATCH 1/6] Enhance LinearProgress component to support tone-based styling for value readout - Updated `LinearProgressValue` to accept a `tone` prop, allowing the percentage readout to match the fill's tone as per design specifications. - Modified related stories to demonstrate the new tone functionality, ensuring consistent visual representation across different progress states. - Adjusted the `linearProgressValueVariants` to include tone variants, improving the semantic color application on the readout. --- .../linear-progress/linear-progress.tsx | 2 +- .../linear-progress/linear-progress-value.tsx | 16 +++++++++------- .../linear-progress.stories.tsx | 8 ++++---- .../src/elements/linear-progress/variants.ts | 18 +++++++++++++++--- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/propel/src/components/linear-progress/linear-progress.tsx b/packages/propel/src/components/linear-progress/linear-progress.tsx index da06ecf3..26a44d1f 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.tsx @@ -54,7 +54,7 @@ export function LinearProgress({ } /> {showValue ? ( - }> + }> {(_, currentValue) => (currentValue == null ? "" : `${Math.round(currentValue)}%`)} ) : null} diff --git a/packages/propel/src/elements/linear-progress/linear-progress-value.tsx b/packages/propel/src/elements/linear-progress/linear-progress-value.tsx index ba9731ba..c3e4f122 100644 --- a/packages/propel/src/elements/linear-progress/linear-progress-value.tsx +++ b/packages/propel/src/elements/linear-progress/linear-progress-value.tsx @@ -1,19 +1,21 @@ import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; -import { linearProgressValueVariants } from "./variants"; +import { linearProgressValueVariants, type LinearProgressValueVariantProps } from "./variants"; export type LinearProgressValueProps = Omit< useRender.ComponentProps<"span">, "className" | "style" ->; +> & + LinearProgressValueVariantProps; /** - * The styled percentage readout — a neutral number (the semantic tone lives on the fill, not on the - * number). Base-UI-agnostic — graft in `components` via `} />`. + * The styled percentage readout — tinted to match the fill `tone` (per Figma). Base-UI-agnostic — + * graft in `components` via `} />`. */ -export function LinearProgressValue({ render, ...props }: LinearProgressValueProps) { - const defaultProps: useRender.ElementProps<"span"> = { className: linearProgressValueVariants() }; +export function LinearProgressValue({ render, tone, ...props }: LinearProgressValueProps) { + const defaultProps: useRender.ElementProps<"span"> = { + className: linearProgressValueVariants({ tone }), + }; return useRender({ defaultTagName: "span", render, props: mergeProps(defaultProps, props) }); } diff --git a/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx b/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx index bc721ad0..d85e3edc 100644 --- a/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx +++ b/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx @@ -73,7 +73,7 @@ export const Default: Story = { render={
} /> - 60% + 60%
), @@ -101,7 +101,7 @@ export const Tones: Story = { render={
} /> - 60% + 60% ))}
@@ -130,7 +130,7 @@ export const Magnitudes: Story = { render={
} /> - 60% + 60% ))}
@@ -165,7 +165,7 @@ export const Values: Story = { render={
} /> - {value}% + {value}% ); })} diff --git a/packages/propel/src/elements/linear-progress/variants.ts b/packages/propel/src/elements/linear-progress/variants.ts index 974fe83f..279e43be 100644 --- a/packages/propel/src/elements/linear-progress/variants.ts +++ b/packages/propel/src/elements/linear-progress/variants.ts @@ -33,9 +33,18 @@ export const linearProgressIndicatorVariants = cva( export const linearProgressLabelVariants = cva("text-13 font-medium text-secondary"); -// The percentage is a neutral readout (matching the label), not a toned signal — the semantic color -// lives on the fill bar. Neutral also avoids low-contrast amber/green readouts on a neutral surface. -export const linearProgressValueVariants = cva("text-12 font-medium text-secondary tabular-nums"); +// The percentage readout is tinted to match the fill `tone` (per Figma) — the semantic color reads +// on both the bar and the number. +export const linearProgressValueVariants = cva("text-12 font-medium tabular-nums", { + variants: { + tone: { + brand: "text-accent-primary", + success: "text-success-primary", + warning: "text-warning-primary", + danger: "text-danger-primary", + }, + }, +}); export type LinearProgressTrackVariantProps = StrictVariantProps< typeof linearProgressTrackVariants @@ -43,3 +52,6 @@ export type LinearProgressTrackVariantProps = StrictVariantProps< export type LinearProgressIndicatorVariantProps = StrictVariantProps< typeof linearProgressIndicatorVariants >; +export type LinearProgressValueVariantProps = StrictVariantProps< + typeof linearProgressValueVariants +>; From 6f0c55e3ce089637c5c04708f2099e183479bde6 Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 20:07:51 +0530 Subject: [PATCH 2/6] Add clamping behavior for out-of-range values in CircularProgress component - Introduced a new story, `ClampsOutOfRange`, to demonstrate the clamping of values exceeding the maximum limit, ensuring `aria-valuenow` reflects the clamped value. - Updated `CircularProgressProps` to exclude the `render` prop, streamlining the component's API. - Enhanced LinearProgress component to support an optional `aria-label` when a `label` is provided, improving accessibility. - Removed unnecessary prop documentation comments in circular progress elements for cleaner code. --- .../circular-progress.stories.tsx | 14 ++++++++++++++ .../circular-progress/circular-progress.tsx | 4 ++-- .../linear-progress/linear-progress.stories.tsx | 16 ++++++++++++++-- .../linear-progress/linear-progress.tsx | 9 ++++++--- .../circular-progress-indicator.tsx | 1 - .../circular-progress/circular-progress-svg.tsx | 1 - .../circular-progress-track.tsx | 1 - .../circular-progress/circular-progress.tsx | 1 - 8 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/propel/src/components/circular-progress/circular-progress.stories.tsx b/packages/propel/src/components/circular-progress/circular-progress.stories.tsx index 21b87d2a..51aff667 100644 --- a/packages/propel/src/components/circular-progress/circular-progress.stories.tsx +++ b/packages/propel/src/components/circular-progress/circular-progress.stories.tsx @@ -66,6 +66,20 @@ export const HasProgressbarRole: Story = { }, }; +/** + * Out-of-range `value` is clamped to `[0, max]` before the arc and `aria-valuenow` are derived, so + * the two never disagree. Tagged out of the sidebar/docs/manifest while still running under + * `test`. + */ +export const ClampsOutOfRange: Story = { + tags: ["!dev", "!autodocs", "!manifest"], + args: { value: 150, "aria-label": "Overshoot" }, + play: async ({ canvas }) => { + const ring = canvas.getByRole("progressbar", { name: "Overshoot" }); + await expect(ring).toHaveAttribute("aria-valuenow", "100"); + }, +}; + /** `value={null}` is indeterminate: a fixed quarter arc spins with no `aria-valuenow`. */ export const Indeterminate: Story = { args: { value: null, magnitude: "md", tone: "brand", "aria-label": "Syncing" }, diff --git a/packages/propel/src/components/circular-progress/circular-progress.tsx b/packages/propel/src/components/circular-progress/circular-progress.tsx index 2525e8f1..d9105782 100644 --- a/packages/propel/src/components/circular-progress/circular-progress.tsx +++ b/packages/propel/src/components/circular-progress/circular-progress.tsx @@ -22,7 +22,7 @@ const RING_STROKE = 2; export type CircularProgressProps = Omit< BaseProgress.Root.Props, - "className" | "style" | "value" + "className" | "style" | "value" | "render" > & { /** * Completion from 0 to `max` (default 100). `null` = indeterminate: a fixed quarter arc spins @@ -56,8 +56,8 @@ export function CircularProgress({ value, magnitude, tone, ...props }: CircularP return ( } {...props} + render={} > diff --git a/packages/propel/src/components/linear-progress/linear-progress.stories.tsx b/packages/propel/src/components/linear-progress/linear-progress.stories.tsx index 7d02dd27..16dbaae4 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.stories.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.stories.tsx @@ -4,6 +4,7 @@ import { expect, waitFor } from "storybook/test"; import { LinearProgressIndicator, + LinearProgressLabel, LinearProgressTrack, LinearProgressValue, } from "../../elements/linear-progress"; @@ -15,7 +16,12 @@ const TONES: LinearProgressTone[] = ["brand", "success", "warning", "danger"]; const meta = { title: "Components/LinearProgress", component: LinearProgress, - subcomponents: { LinearProgressTrack, LinearProgressIndicator, LinearProgressValue }, + subcomponents: { + LinearProgressLabel, + LinearProgressTrack, + LinearProgressIndicator, + LinearProgressValue, + }, args: { value: 60, magnitude: "md", tone: "brand", "aria-label": "Upload progress" }, // The bar has no intrinsic width (`w-full` root, `flex-1 min-w-0` track), so a bare render // collapses to 0px under the centered layout — give every story a real width to fill. @@ -106,5 +112,11 @@ export const HasProgressbarRole: Story = { /** A visible text label before the track — `label` also names the bar for assistive tech. */ export const WithLabel: Story = { - args: { value: 60, magnitude: "md", tone: "brand", label: "Uploading attachments" }, + args: { + value: 60, + magnitude: "md", + tone: "brand", + label: "Uploading attachments", + "aria-label": undefined, + }, }; diff --git a/packages/propel/src/components/linear-progress/linear-progress.tsx b/packages/propel/src/components/linear-progress/linear-progress.tsx index 26a44d1f..366ad169 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.tsx @@ -13,7 +13,10 @@ import { export type LinearProgressMagnitude = NonNullable; export type LinearProgressTone = NonNullable; -export type LinearProgressProps = Omit & { +export type LinearProgressProps = Omit< + BaseProgress.Root.Props, + "className" | "style" | "value" | "render" +> & { /** * Completion from 0 to `max` (default 100). `null` = indeterminate (animated fill, * `aria-valuenow` unset). @@ -27,8 +30,8 @@ export type LinearProgressProps = Omit, "className" | "style" diff --git a/packages/propel/src/elements/circular-progress/circular-progress-svg.tsx b/packages/propel/src/elements/circular-progress/circular-progress-svg.tsx index c2133586..02322c98 100644 --- a/packages/propel/src/elements/circular-progress/circular-progress-svg.tsx +++ b/packages/propel/src/elements/circular-progress/circular-progress-svg.tsx @@ -3,7 +3,6 @@ import { useRender } from "@base-ui/react/use-render"; import { circularProgressSvgVariants } from "./variants"; -/** Props for {@link CircularProgressSvg}. */ export type CircularProgressSvgProps = Omit, "className" | "style">; /** diff --git a/packages/propel/src/elements/circular-progress/circular-progress-track.tsx b/packages/propel/src/elements/circular-progress/circular-progress-track.tsx index 894a76f1..90ab34b3 100644 --- a/packages/propel/src/elements/circular-progress/circular-progress-track.tsx +++ b/packages/propel/src/elements/circular-progress/circular-progress-track.tsx @@ -3,7 +3,6 @@ import { useRender } from "@base-ui/react/use-render"; import { circularProgressTrackVariants } from "./variants"; -/** Props for {@link CircularProgressTrack}. */ export type CircularProgressTrackProps = Omit< useRender.ComponentProps<"circle">, "className" | "style" diff --git a/packages/propel/src/elements/circular-progress/circular-progress.tsx b/packages/propel/src/elements/circular-progress/circular-progress.tsx index 62d69b52..c7bcbe7d 100644 --- a/packages/propel/src/elements/circular-progress/circular-progress.tsx +++ b/packages/propel/src/elements/circular-progress/circular-progress.tsx @@ -3,7 +3,6 @@ import { useRender } from "@base-ui/react/use-render"; import { type CircularProgressVariantProps, circularProgressVariants } from "./variants"; -/** Props for {@link CircularProgress}. */ export type CircularProgressProps = Omit, "className" | "style"> & CircularProgressVariantProps; From 0e271a4d272cd9e0c65999cf1c0d99994ec35d65 Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 23:09:38 +0530 Subject: [PATCH 3/6] Refactor LinearProgressValue to remove tone prop and update styling - Removed the `tone` prop from `LinearProgressValue`, simplifying the component's API and ensuring the percentage readout is a neutral number. - Updated the `linearProgressValueVariants` to reflect the change, aligning the styling with the design specifications that emphasize a neutral readout. - Adjusted related stories to remove tone references, ensuring consistent representation across different progress states. --- .../linear-progress/linear-progress.tsx | 2 +- .../linear-progress/linear-progress-value.tsx | 16 +++++++--------- .../linear-progress.stories.tsx | 8 ++++---- .../src/elements/linear-progress/variants.ts | 19 ++++--------------- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/packages/propel/src/components/linear-progress/linear-progress.tsx b/packages/propel/src/components/linear-progress/linear-progress.tsx index 366ad169..e0a3c4ed 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.tsx @@ -57,7 +57,7 @@ export function LinearProgress({ } /> {showValue ? ( - }> + }> {(_, currentValue) => (currentValue == null ? "" : `${Math.round(currentValue)}%`)} ) : null} diff --git a/packages/propel/src/elements/linear-progress/linear-progress-value.tsx b/packages/propel/src/elements/linear-progress/linear-progress-value.tsx index c3e4f122..ba9731ba 100644 --- a/packages/propel/src/elements/linear-progress/linear-progress-value.tsx +++ b/packages/propel/src/elements/linear-progress/linear-progress-value.tsx @@ -1,21 +1,19 @@ import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; -import { linearProgressValueVariants, type LinearProgressValueVariantProps } from "./variants"; +import { linearProgressValueVariants } from "./variants"; export type LinearProgressValueProps = Omit< useRender.ComponentProps<"span">, "className" | "style" -> & - LinearProgressValueVariantProps; +>; /** - * The styled percentage readout — tinted to match the fill `tone` (per Figma). Base-UI-agnostic — - * graft in `components` via `} />`. + * The styled percentage readout — a neutral number (the semantic tone lives on the fill, not on the + * number). Base-UI-agnostic — graft in `components` via `} />`. */ -export function LinearProgressValue({ render, tone, ...props }: LinearProgressValueProps) { - const defaultProps: useRender.ElementProps<"span"> = { - className: linearProgressValueVariants({ tone }), - }; +export function LinearProgressValue({ render, ...props }: LinearProgressValueProps) { + const defaultProps: useRender.ElementProps<"span"> = { className: linearProgressValueVariants() }; return useRender({ defaultTagName: "span", render, props: mergeProps(defaultProps, props) }); } diff --git a/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx b/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx index d85e3edc..bc721ad0 100644 --- a/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx +++ b/packages/propel/src/elements/linear-progress/linear-progress.stories.tsx @@ -73,7 +73,7 @@ export const Default: Story = { render={
} /> - 60% + 60%
), @@ -101,7 +101,7 @@ export const Tones: Story = { render={
} /> - 60% + 60% ))}
@@ -130,7 +130,7 @@ export const Magnitudes: Story = { render={
} /> - 60% + 60% ))}
@@ -165,7 +165,7 @@ export const Values: Story = { render={
} /> - {value}% + {value}% ); })} diff --git a/packages/propel/src/elements/linear-progress/variants.ts b/packages/propel/src/elements/linear-progress/variants.ts index 279e43be..2b257dce 100644 --- a/packages/propel/src/elements/linear-progress/variants.ts +++ b/packages/propel/src/elements/linear-progress/variants.ts @@ -33,18 +33,10 @@ export const linearProgressIndicatorVariants = cva( export const linearProgressLabelVariants = cva("text-13 font-medium text-secondary"); -// The percentage readout is tinted to match the fill `tone` (per Figma) — the semantic color reads -// on both the bar and the number. -export const linearProgressValueVariants = cva("text-12 font-medium tabular-nums", { - variants: { - tone: { - brand: "text-accent-primary", - success: "text-success-primary", - warning: "text-warning-primary", - danger: "text-danger-primary", - }, - }, -}); +// The percentage is a neutral readout (matching the label), not a toned signal — the semantic color +// lives on the fill bar. Neutral also keeps the small 12px number AA-legible: the warning amber +// text token lands at 4.49:1 on the neutral surface (below WCAG AA), so tinting the % is not viable. +export const linearProgressValueVariants = cva("text-12 font-medium text-secondary tabular-nums"); export type LinearProgressTrackVariantProps = StrictVariantProps< typeof linearProgressTrackVariants @@ -52,6 +44,3 @@ export type LinearProgressTrackVariantProps = StrictVariantProps< export type LinearProgressIndicatorVariantProps = StrictVariantProps< typeof linearProgressIndicatorVariants >; -export type LinearProgressValueVariantProps = StrictVariantProps< - typeof linearProgressValueVariants ->; From 60c9ac171de56045a81334ed4d7a18aa05d1c608 Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 23:19:31 +0530 Subject: [PATCH 4/6] Refactor LinearProgress component to improve type safety and accessibility - Changed the type definition for `LinearProgressProps` to enforce a union of `label` and `aria-label`, ensuring that one of them is always provided for accessibility. - Updated the story type to be directly tied to `LinearProgress`, enhancing type-checking for individual stories. - Added documentation comments to clarify the new prop requirements and improve code readability. --- .../linear-progress.stories.tsx | 5 +++- .../linear-progress/linear-progress.tsx | 28 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/propel/src/components/linear-progress/linear-progress.stories.tsx b/packages/propel/src/components/linear-progress/linear-progress.stories.tsx index 16dbaae4..4e3567b8 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.stories.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.stories.tsx @@ -35,7 +35,10 @@ const meta = { } satisfies Meta; export default meta; -type Story = StoryObj; +// Typed against the component (not `typeof meta`): the props are a discriminated union on +// `label`/`aria-label`, which `StoryObj` collapses to `never`. `StoryObj` keeps per-story args as a `Partial` of the union so stories still type-check. +type Story = StoryObj; export const Default: Story = {}; diff --git a/packages/propel/src/components/linear-progress/linear-progress.tsx b/packages/propel/src/components/linear-progress/linear-progress.tsx index e0a3c4ed..0d85560a 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.tsx @@ -13,9 +13,9 @@ import { export type LinearProgressMagnitude = NonNullable; export type LinearProgressTone = NonNullable; -export type LinearProgressProps = Omit< +type LinearProgressOwnProps = Omit< BaseProgress.Root.Props, - "className" | "style" | "value" | "render" + "className" | "style" | "value" | "render" | "aria-label" > & { /** * Completion from 0 to `max` (default 100). `null` = indeterminate (animated fill, @@ -28,12 +28,28 @@ export type LinearProgressProps = Omit< tone: LinearProgressTone; /** Show the trailing percentage label. @default true */ showValue?: boolean; - /** Visible text label before the track — Base UI's `Progress.Label` (also names the bar). */ - label?: string; - /** Accessible name. Required unless `label` is set — then the visible `label` names the bar. */ - "aria-label"?: string; }; +/** + * The bar always has an accessible name: pass a visible `label` (names it via Base UI's + * `Progress.Label`) or an `aria-label` — the union makes omitting both a type error rather than a + * silently unnamed progressbar. + */ +export type LinearProgressProps = LinearProgressOwnProps & + ( + | { + /** Visible text label before the track — Base UI's `Progress.Label` (also names the bar). */ + label: string; + /** Accessible name. Optional when `label` is set — the visible label already names the bar. */ + "aria-label"?: string; + } + | { + label?: undefined; + /** Accessible name (required when there is no visible `label`). */ + "aria-label": string; + } + ); + /** * A horizontal determinate/indeterminate progress bar with an optional trailing `%` label. Drive it * with `value` (0–`max`); the fill and `aria-valuenow` follow. For a ring, use `CircularProgress`. From c765521f2ce4658b1ad21b182deaa61057bd9852 Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 23:24:50 +0530 Subject: [PATCH 5/6] Enhance CircularProgress component to support min/max clamping for value calculation - Introduced `min` prop to allow for customizable lower bounds in value clamping, ensuring accurate representation of progress. - Updated the clamping logic to derive the fraction based on both `min` and `max`, improving the component's flexibility and correctness. - Enhanced comments for clarity on the clamping behavior and its impact on accessibility attributes. --- .../circular-progress/circular-progress.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/propel/src/components/circular-progress/circular-progress.tsx b/packages/propel/src/components/circular-progress/circular-progress.tsx index d9105782..57b22330 100644 --- a/packages/propel/src/components/circular-progress/circular-progress.tsx +++ b/packages/propel/src/components/circular-progress/circular-progress.tsx @@ -47,10 +47,13 @@ export function CircularProgress({ value, magnitude, tone, ...props }: CircularP const { box, radius } = RING_GEOMETRY[magnitude]; const circumference = 2 * Math.PI * radius; const max = props.max ?? 100; - // Clamp once so the arc and `aria-valuenow` never disagree for out-of-range input. While - // indeterminate a fixed quarter arc shows; the svg part spins it off `data-indeterminate`. - const clampedValue = value == null ? null : Math.min(Math.max(value, 0), max); - const fraction = clampedValue == null ? 0.25 : max > 0 ? clampedValue / max : 0; + const min = props.min ?? 0; + const span = max - min; + // Clamp once so the arc and `aria-valuenow` never disagree for out-of-range input, and derive the + // fraction from both bounds so a non-zero `min` maps correctly. While indeterminate a fixed + // quarter arc shows; the svg part spins it off `data-indeterminate`. + const clampedValue = value == null ? null : Math.min(Math.max(value, min), max); + const fraction = clampedValue == null ? 0.25 : span > 0 ? (clampedValue - min) / span : 0; const dashOffset = circumference * (1 - fraction); const center = box / 2; return ( From cb56f85c2742671a93955cbd444706472e88c417 Mon Sep 17 00:00:00 2001 From: Atul Tameshwari Date: Wed, 15 Jul 2026 23:27:18 +0530 Subject: [PATCH 6/6] Clamp out-of-range values in LinearProgress component for accurate progress representation - Introduced `min` and `max` props to allow customizable bounds for value clamping, ensuring that the displayed progress does not exceed defined limits. - Updated clamping logic to prevent discrepancies between the displayed value and accessibility attributes, enhancing overall component reliability. - Improved comments for clarity on the clamping behavior and its implications for accessibility. --- .../src/components/linear-progress/linear-progress.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/propel/src/components/linear-progress/linear-progress.tsx b/packages/propel/src/components/linear-progress/linear-progress.tsx index 0d85560a..823448c4 100644 --- a/packages/propel/src/components/linear-progress/linear-progress.tsx +++ b/packages/propel/src/components/linear-progress/linear-progress.tsx @@ -64,8 +64,13 @@ export function LinearProgress({ label, ...props }: LinearProgressProps) { + const max = props.max ?? 100; + const min = props.min ?? 0; + // Clamp out-of-range input to `[min, max]` before Base UI derives the fill, `aria-valuenow`, and + // the `%` label, so they never disagree (e.g. `value={150}` reads 100%, not a 150% overflow). + const clampedValue = value == null ? null : Math.min(Math.max(value, min), max); return ( - }> + }> {label != null ? ( }>{label} ) : null}