Progress
Shows how far a task is. Label and value parts; brand variant.
import { Progress, ProgressLabel, ProgressValue,} from "@/components/ui/progress";import * as React from "react";
export default function Demo() { const [value, setValue] = React.useState(20); React.useEffect(() => { const id = setInterval( () => setValue((v) => (v >= 100 ? 10 : v + 10)), 1200, ); return () => clearInterval(id); }, []); return ( <div className="flex w-80 flex-col gap-6"> <Progress value={value} /> <Progress value={value} variant="brand"> <ProgressLabel>Raising for launch</ProgressLabel> <ProgressValue /> </Progress> </div> );}<script setup lang="ts">import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress";import { onBeforeUnmount, onMounted, ref } from "vue";
const value = ref(20);let id: ReturnType<typeof setInterval> | undefined;onMounted(() => { id = setInterval(() => { value.value = value.value >= 100 ? 10 : value.value + 10; }, 1200);});onBeforeUnmount(() => clearInterval(id));</script>
<template> <div class="flex w-80 flex-col gap-6"> <Progress :model-value="value" /> <Progress :model-value="value" variant="brand"> <ProgressLabel>Raising for launch</ProgressLabel> <ProgressValue /> </Progress> </div></template><script lang="ts"> import * as Progress from "$lib/components/ui/progress"; import { onMount } from "svelte";
let value = $state(20); onMount(() => { const id = setInterval(() => { value = value >= 100 ? 10 : value + 10; }, 1200); return () => clearInterval(id); });</script>
<div class="flex w-80 flex-col gap-6"> <Progress.Root {value} /> <Progress.Root {value} variant="brand"> <Progress.Label>Raising for launch</Progress.Label> <Progress.Value /> </Progress.Root></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/progresspnpm dlx shadcn@latest add @edmi-ui/progressyarn dlx shadcn@latest add @edmi-ui/progressbunx shadcn@latest add @edmi-ui/progressPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/progresspnpm dlx shadcn-vue@latest add @edmi-ui/progressyarn dlx shadcn-vue@latest add @edmi-ui/progressbunx shadcn-vue@latest add @edmi-ui/progressPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreCopy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/progress.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/progress.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/progress.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/progress.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Copy the code from Component source into your project and update the import paths to match your aliases.
import { Progress, ProgressLabel, ProgressValue,} from "@/components/ui/progress";
<div className="flex w-80 flex-col gap-6"> <Progress /> <Progress variant="brand"> <ProgressLabel>Raising for launch</ProgressLabel> <ProgressValue /> </Progress></div><script setup lang="ts">import { Progress, ProgressLabel, ProgressValue,} from "@/components/ui/progress";</script>
<template> <div class="flex w-80 flex-col gap-6"> <Progress /> <Progress variant="brand"> <ProgressLabel>Raising for launch</ProgressLabel> <ProgressValue /> </Progress> </div></template><script lang="ts"> import * as Progress from "$lib/components/ui/progress";</script>
<div class="flex w-80 flex-col gap-6"> <Progress.Root /> <Progress.Root variant="brand"> <Progress.Label>Raising for launch</Progress.Label> <Progress.Value /> </Progress.Root></div>Component source
Section titled “Component source”import { Progress as ProgressPrimitive } from "@base-ui/react/progress";import { cn } from "cn";
function Progress({ className, children, value, variant = "default", ...props}: ProgressPrimitive.Root.Props & { variant?: "default" | "brand" }) { return ( <ProgressPrimitive.Root value={value} data-slot="progress" data-variant={variant} className={cn("flex flex-wrap gap-3", className)} {...props} > {children} <ProgressTrack> <ProgressIndicator /> </ProgressTrack> </ProgressPrimitive.Root> );}
// 8px track: muted fill + 1px border, indicator is --primary (✦ variant="brand" on Progress).function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) { return ( <ProgressPrimitive.Track className={cn( "relative flex h-2 w-full items-center overflow-hidden rounded-full border border-border bg-muted", className, )} data-slot="progress-track" {...props} /> );}
function ProgressIndicator({ className, ...props}: ProgressPrimitive.Indicator.Props) { return ( <ProgressPrimitive.Indicator data-slot="progress-indicator" className={cn( "h-full rounded-full bg-primary transition-all [[data-variant=brand]_&]:bg-brand", className, )} {...props} /> );}
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) { return ( <ProgressPrimitive.Label className={cn("text-sm font-medium", className)} data-slot="progress-label" {...props} /> );}
function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) { return ( <ProgressPrimitive.Value className={cn( "ml-auto font-mono text-[13px] text-muted-foreground tabular-nums", className, )} data-slot="progress-value" {...props} /> );}
export { Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue,};<script setup lang="ts">import type { ProgressRootProps } from 'reka-ui'import type { HTMLAttributes } from 'vue'import { reactiveOmit } from '@vueuse/core'import { ProgressIndicator, ProgressRoot } from 'reka-ui'import { computed, provide } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import { progressPercentKey } from './context'
const props = withDefaults( defineProps<ProgressRootProps & { class?: HTMLAttributes['class'] variant?: 'default' | 'brand' }>(), { modelValue: 0, variant: 'default', },)
const delegatedProps = reactiveOmit(props, 'class', 'variant')
const percent = computed(() => props.modelValue == null ? null : Math.round((props.modelValue / (props.max ?? 100)) * 100),)provide(progressPercentKey, percent)</script>
<template> <ProgressRoot data-slot="progress" :data-variant="variant" v-bind="delegatedProps" :class="cn('flex flex-wrap gap-3', props.class)" > <slot /> <!-- 8px track: muted fill + 1px border, indicator is --primary (✦ variant="brand"). --> <div data-slot="progress-track" class="relative flex h-2 w-full items-center overflow-hidden rounded-full border border-border bg-muted" > <ProgressIndicator data-slot="progress-indicator" class="h-full w-full flex-1 rounded-full bg-primary transition-all [[data-variant=brand]_&]:bg-brand" :style="`transform: translateX(-${100 - (percent ?? 0)}%);`" /> </div> </ProgressRoot></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class']}>()</script>
<template> <span data-slot="progress-label" :class="cn('text-sm font-medium', props.class)" > <slot /> </span></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { inject } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import { progressPercentKey } from './context'
const props = defineProps<{ class?: HTMLAttributes['class']}>()
const percent = inject(progressPercentKey, undefined)</script>
<template> <span data-slot="progress-value" :class="cn('ml-auto font-mono text-[13px] text-muted-foreground tabular-nums', props.class)" > <slot>{{ percent == null ? "" : `${percent}%` }}</slot> </span></template>import type { ComputedRef, InjectionKey } from "vue";
export const progressPercentKey: InjectionKey<ComputedRef<number | null>> = Symbol("edmi-progress-percent");export { default as Progress } from "./Progress.vue";export { default as ProgressLabel } from "./ProgressLabel.vue";export { default as ProgressValue } from "./ProgressValue.vue";import { getContext, setContext } from "svelte";
const key = Symbol("edmi-progress-percent");
/** Getter for the current percent (0-100), or null when indeterminate. */export function setProgressPercent(get: () => number | null) { setContext(key, get);}
export function getProgressPercent(): () => number | null { return getContext<(() => number | null) | undefined>(key) ?? (() => null);}<script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();</script>
<span bind:this={ref} data-slot="progress-label" class={cn( "text-sm font-medium", className )} {...restProps}> {@render children?.()}</span><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements"; import { getProgressPercent } from "./context.js";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
const percent = getProgressPercent();</script>
<span bind:this={ref} data-slot="progress-value" class={cn( "ml-auto font-mono text-[13px] text-muted-foreground tabular-nums", className )} {...restProps}> {#if children}{@render children()}{:else if percent() != null}{percent()}%{/if}</span><script lang="ts"> import { Progress as ProgressPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import { setProgressPercent } from "./context.js";
let { ref = $bindable(null), class: className, max = 100, value, variant = "default", children, ...restProps }: Omit<ProgressPrimitive.RootProps, "child"> & { variant?: "default" | "brand" } = $props();
const percent = $derived(value == null ? null : Math.round((100 * value) / (max ?? 1))); setProgressPercent(() => percent);</script>
<ProgressPrimitive.Root bind:ref data-slot="progress" data-variant={variant} class={cn("flex flex-wrap gap-3", className)} {value} {max} {...restProps}> {@render children?.()} <!-- 8px track: muted fill + 1px border, indicator is --primary (✦ variant="brand"). --> <div data-slot="progress-track" class="relative flex h-2 w-full items-center overflow-hidden rounded-full border border-border bg-muted"> <div data-slot="progress-indicator" class="h-full w-full flex-1 rounded-full bg-primary transition-all [[data-variant=brand]_&]:bg-brand" style="transform: translateX(-{100 - (percent ?? 0)}%)" ></div> </div></ProgressPrimitive.Root>import Root from "./progress.svelte";import Label from "./progress-label.svelte";import Value from "./progress-value.svelte";
export { Label, Label as ProgressLabel, Root, // Root as Progress, Value, Value as ProgressValue,};| Prop | Type | Default |
|---|---|---|
value |
number | null |
null |
variant |
default | brand |
default |
Parts: Progress, ProgressLabel, ProgressValue, ProgressTrack, ProgressIndicator.
✦ Edmi additions
Section titled “✦ Edmi additions”variant="brand"fills with--brand; track is 8px with a 1px border; value is mono.