Priority bars
Three rising bars whose fills are numbers, so any priority springs to any other.
Priority bars is a small mark for how urgent a task is: three rising bars that fill from the
left as the priority goes up. Each bar's fill is a number on one spring, so any priority
retargets any other, including mid-flight, and it is not five swapped icons or a CSS
data-level snap. It is inked with system roles, and it names itself as an image, so it can
sit in a row on its own or under a Chip trigger.
None
Click Advance, jump to a level, or hit Interrupt while a spring is in flight. Play cycle retargets every 220ms so the next priority lands mid-spring.
Usage
import { PriorityBars } from '@/components/ui/priority-bars';
<PriorityBars priority="high" />import { PriorityBars } from '@/components/ui/priority-bars';
<button type="button" onClick={() => setPriority("urgent")}>
<PriorityBars priority={priority} size="lg" />
</button>The priorities are none, low, medium, high, and urgent. TASK_PRIORITY_LABELS holds
the display text for each and PRIORITY_INK holds the color each is drawn in, as a CSS
variable.
Five levels on three bars
Bars are filled from the left, and the unfilled part of each bar stays as a ghost track at 32%
opacity (--alpha-32), so none still reads as three bars.
| Priority | Bars filled | Ink |
|---|---|---|
none | 0 | --sys-fg-subtle |
low | 1 | --sys-fg-muted |
medium | 2 | --sys-cat-amber |
high | 3 | --sys-cat-amber |
urgent | 3, all at full height | --destructive |
Medium and high share the warning color, and bar count tells them apart. Urgent stands all three bars at full height instead of rising, so it differs from high by shape as well as ink.
The five levels. Mount jumps, so a list does not play in on first paint.
Three sizes
sm is 12px tall, md is 14px and the default, and lg is 18px. The bars get slightly wider
with the size, from 2px at sm to 3px at lg.
Name it once
The mark is an image named Priority Low, Priority High, and so on, and it shows the same
text as a native tooltip. That name is right when the mark is the only signal in the row. When
the priority is also written next to it, a screen reader reads it twice, so pass label="" to
hide the mark and let the text name the priority. Pass your own label for a translated name.
Inside a chip the same rule applies, and the fix is the trigger's aria-label, as described on
Chip trigger.
Motion
One story: priority to priority on a shared spring (stiffness 420 / damping 40 /
mass 0.9), the same one Health face and
Work mark use. It is faster than a
--duration-base fade, with no bounce and no whole-mark fade.
| Rule | What happens |
|---|---|
| Fill | scaleY and opacity, with the origin at the bottom. The ghost track stays at 32%. |
| Level up | Changing bars stagger left to right, 28ms apart (PRIORITY_BARS_STAGGER_MS). |
| Level down | Changing bars stagger right to left. The first change is still immediate. |
| Color | Medium and high share --sys-cat-amber, and urgent is --destructive. |
| Mount | initial={false}, and the springs start on the current numbers. |
| Reduced motion | jump to the end state, and the stagger drops to 0. |
| Interrupt | A new priority retargets the same springs. |
See Motion for the overlay rules. Priority bars is a spring exception, and Tabs is the stretch-line exception.
Props
Priority bars takes only these props, and it forwards its ref to the root <span>.
| Prop | Type | Default | What it does |
|---|---|---|---|
priority | "none" | "low" | "medium" | "high" | "urgent" | Required. Sets the bars, the ink, and the accessible name. | |
size | "sm" | "md" | "lg" | "md" | 12, 14, or 18px tall. |
label | string | Priority {level} | The accessible name and tooltip. "" hides the mark from assistive tech. |
className | string | Added to the root <span>. |
Accessibility
- The root is a
<span role="img">whose accessible name is built from the priority, likePriority High, and it carries the same text as atitle, which browsers show as a tooltip on hover. The three bars inside arearia-hidden.labelreplaces the name, for a translated one. - When text beside the mark already says the priority, the image name repeats it, so pass
label="", which makes the markaria-hiddenand drops the tooltip. A chip with Priority bars as itsmarkand noaria-labelis read as "Priority High High", so give the trigger one that names the property and the value. - Bar count and shape carry the state, and color adds to it. Urgent's bars stand at full
height where high's rise, so the pair holds up in grayscale and color blindness. In forced
colors every ink becomes
CanvasText, and the five levels are still told apart by bar count and shape. - The mark is not focusable and has no press behavior. Put it inside a button for that, and the button's name then includes the image name.
- It is not an interactive target, so it has no 44px minimum. Size the control around it.
- Under reduced motion the fills and the ink jump to the new priority with no transition and no stagger.
When to use something else
- Badge for a priority written as a word, in a role color. It reads as text, so it needs no wrapper to avoid a double reading.
- Category colors for a priority the user defines and colors themselves, like a select option named High, where the color stays the same in every look.
- Health face for how a project is going and Work mark for where work stands, which are the other two marks drawn the same way.
- Check mark for a plain completed check, not a graded level.
- Avatar for a person, such as the assignee beside the priority.
Install
shadcn add @gesso/priority-bars