<script setup lang="ts">
import { ref } from 'vue'
import {
Meter,
MeterHeader,
MeterLabel,
MeterTrack,
MeterIndicator,
MeterValue,
} from '@/components/ui/meter'
const value = ref(75)
</script>
<template>
<div class="flex gap-8 max-w-64 size-full min-h-64 items-center">
<div class="size-full flex-1">
<Meter :value="value" orientation="horizontal" size="lg">
<MeterHeader>
<MeterLabel>Completion</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</Meter>
</div>
</div>
</template>The Meter component displays a value within a known range using a visual track and indicator. It supports multiple color variants and animated transitions.
Installation
pnpm dlx sulaf@latest add meter
Usage
Basic Usage
A meter requires a root Meter component with a value, a MeterTrack containing a MeterIndicator, and an optional MeterHeader with label and value display.
<script setup lang="ts">
import { ref } from 'vue'
import {
Meter,
MeterHeader,
MeterLabel,
MeterTrack,
MeterIndicator,
MeterValue,
} from '@/components/ui/meter'
const value = ref(75)
</script>
<template>
<Meter :value="value">
<MeterHeader>
<MeterLabel>Completion</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</Meter>
</template>Examples
Variants and Sizes
Demonstrates the default, success, warning, danger variants, and sm, default, lg sizes.
Horizontal Meters
Vertical Meters
Different Sizes (Horizontal)
<script setup lang="ts">
import { ref } from 'vue'
import {
Meter,
MeterHeader,
MeterLabel,
MeterTrack,
MeterIndicator,
MeterValue,
} from '@/components/ui/meter'
const value = ref(75)
</script>
<template>
<div class="space-y-8">
<!-- Horizontal Meters -->
<div class="flex flex-col gap-4">
<h3 class="text-lg font-semibold">Horizontal Meters</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Meter :value="value" variant="default">
<MeterHeader>
<MeterLabel>Default</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
<Meter :value="value" variant="success">
<MeterHeader>
<MeterLabel>Success</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
<Meter :value="value" variant="warning">
<MeterHeader>
<MeterLabel>Warning</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
<Meter :value="value" variant="danger">
<MeterHeader>
<MeterLabel>Danger</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
</div>
</div>
<!-- Vertical Meters -->
<div class="flex flex-col gap-4">
<h3 class="text-lg font-semibold">Vertical Meters</h3>
<div class="flex gap-4 h-64">
<Meter :value="value" orientation="vertical" variant="default">
<MeterTrack><MeterIndicator /></MeterTrack>
<MeterHeader>
<MeterLabel>Default</MeterLabel>
<MeterValue />
</MeterHeader>
</Meter>
<Meter :value="value" orientation="vertical" variant="success">
<MeterTrack><MeterIndicator /></MeterTrack>
<MeterHeader>
<MeterLabel>Success</MeterLabel>
<MeterValue />
</MeterHeader>
</Meter>
<Meter :value="value" orientation="vertical" variant="warning">
<MeterTrack><MeterIndicator /></MeterTrack>
<MeterHeader>
<MeterLabel>Warning</MeterLabel>
<MeterValue />
</MeterHeader>
</Meter>
<Meter :value="value" orientation="vertical" variant="danger">
<MeterTrack><MeterIndicator /></MeterTrack>
<MeterHeader>
<MeterLabel>Danger</MeterLabel>
<MeterValue />
</MeterHeader>
</Meter>
</div>
</div>
<!-- Different Sizes -->
<div class="flex flex-col gap-4">
<h3 class="text-lg font-semibold">Different Sizes (Horizontal)</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<Meter :value="value" size="sm">
<MeterHeader>
<MeterLabel>Small</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
<Meter :value="value" size="default">
<MeterHeader>
<MeterLabel>Default</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
<Meter :value="value" size="lg">
<MeterHeader>
<MeterLabel>Large</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack><MeterIndicator /></MeterTrack>
</Meter>
</div>
</div>
</div>
</template>Dynamic Meter
Showcases a meter with a dynamically changing value and variant, useful for progress displays.
This meter updates its value and variant dynamically to reflect progress.
<script setup lang="ts">
import { ref, onMounted, shallowRef, onUnmounted } from 'vue'
import {
Meter,
MeterHeader,
MeterLabel,
MeterTrack,
MeterIndicator,
MeterValue,
} from '@/components/ui/meter'
import { Button } from '@/components/ui/button'
const dynamicValue = ref(0)
const interval = shallowRef<ReturnType<typeof setInterval> | null>(null)
const startProgress = () => {
if (interval.value) clearInterval(interval.value)
dynamicValue.value = 0
interval.value = setInterval(() => {
if (dynamicValue.value < 100) {
dynamicValue.value += 10
} else {
if (interval.value) clearInterval(interval.value)
interval.value = null
}
}, 500)
}
onMounted(() => {
startProgress()
})
onUnmounted(() => {
if (interval.value) clearInterval(interval.value)
})
const getMeterVariant = (value: number) => {
if (value < 40) return 'danger'
if (value < 70) return 'warning'
if (value < 100) return 'default'
return 'success'
}
</script>
<template>
<div class="w-full max-w-lg space-y-6">
<Meter :value="dynamicValue" :variant="getMeterVariant(dynamicValue)">
<MeterHeader>
<MeterLabel>Dynamic Progress</MeterLabel>
<MeterValue />
</MeterHeader>
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</Meter>
<Button @click="startProgress" :disabled="dynamicValue < 100 && interval !== null">
+
{{ dynamicValue < 100 && interval !== null ? 'In Progress...' : 'Restart Progress' }}
</Button>
<p class="text-sm text-muted-foreground">
This meter updates its value and variant dynamically to reflect progress.
</p>
</div>
</template>Custom Styling
You can combine data slots, ARIA attributes, and state-based data attributes to create highly specific styles:
/* Style the indicator based on the root's variant and value */
[data-slot="meter"][data-variant="danger"][aria-valuenow="100"] [data-slot="meter-indicator"] {
background-color: #ef4444;
box-shadow: 0 0 10px #ef4444;
}
/* Change the label color when the meter reaches its maximum value */
[data-slot="meter"][data-percentage="100%"] [data-slot="meter-label"] {
color: #10b981;
}
/* Target the value text when it matches a specific ARIA text representation */
[data-slot="meter"][aria-valuetext="50%"] [data-slot="meter-value"] {
font-weight: 700;
text-decoration: underline;
}Accessibility
The Meter component is designed with accessibility in mind, adhering to WAI-ARIA guidelines for progress indicators. It uses the meter role and relevant ARIA attributes to convey its state to assistive technologies.
ARIA Attributes
| Attribute | Description |
|---|---|
role | The Meter root component uses role="meter". |
aria-valuenow | Represents the current value of the meter (from value prop). |
aria-valuemin | Defines the minimum allowed value (from min prop). |
aria-valuemax | Defines the maximum allowed value (from max prop). |
aria-valuetext | Provides a human-readable text alternative for the current value. By default, this is set to the percentage computed property (e.g., "50%"). For more context-specific information (e.g., "50 percent used", "12 GB remaining"), consider overriding this by providing a descriptive label prop to the Meter component. |
| aria-label | An optional accessible label for the meter (from label prop). |
Data Attributes
The component also exposes several data attributes for styling and testing purposes:
| Attribute | Description |
|---|---|
data-variant | Reflects the variant prop for style targeting. |
data-size | Reflects the size prop for size-specific styles. |
data-value | Current numeric value (from value prop). |
data-max | Maximum allowed value (from max prop). |
data-min | Minimum allowed value (from min prop). |
data-percentage | Computed percentage value (0-100). |
Data Slots
Use these slots to target specific parts of the component for styling:
| Attribute | Description |
|---|---|
data-slot="meter" | The root Meter component. |
data-slot="meter-header" | Container for the label and value. |
data-slot="meter-label" | The label component. |
data-slot="meter-value" | The value display component. |
data-slot="meter-track" | The background track component. |
data-slot="meter-indicator" | The visual indicator component. |