Health face
Phosphor's filled smiley with a mouth that springs between on track, at risk, and off track.
Health face is a small mark for the health of an initiative or project. It starts from
Phosphor's Smiley at fill weight, and the mouth is one number (1 smile, 0 flat, -1 frown) on
a spring, so any health retargets any other. A jump from on track to off track passes through
flat on the way, which reads as the project sliding rather than swapping icons. It is inked
with the system's success, warning, and danger colors, and it names itself as an image, so it
can sit in a row on its own or under a
Chip trigger as its mark.
On track
Advance steps through the three, and a jump button skips straight across. Play cycle retargets every 700ms.
Usage
import { HealthFace } from '@/components/ui/health-face';
<HealthFace health="at_risk" />The health values are on_track, at_risk, and off_track. To put the state in words
beside the mark, wrap the mark so the state is read once, as described in
Name it once.
import { HEALTH_LABELS, HealthFace } from '@/components/ui/health-face';
<span className="inline-flex items-center gap-1.5">
<HealthFace health={health} label="" />
{HEALTH_LABELS[health]}
</span>HEALTH_LABELS holds the display text for each state and HEALTH_INK holds the color each
state is drawn in, as a CSS variable.
Size follows the text
size defaults to 1em, so the mark matches the text beside it. Pass a number for pixels or a
string for any other CSS length. Keep it at 16px or larger, where the mouth stroke is a full
pixel, because below that the three mouths get hard to tell apart. In a sm chip, 1em is only
12px, so match the chip's icon scale with className="size-4", as the property band does.
The three states at 16, 24, and 40px. Mount jumps, so a list does not play in on first paint.
Name it once
The mark is an image named Health On track, Health At risk, or Health Off track. That name
is right when the mark is the only signal in the row. When the state is also written next to it,
a screen reader reads it twice, so pass label="" to hide the mark from assistive tech and let
the text name the state. 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. The ink is chosen for the mark, so keep
the label in neutral text unless you have checked the ink as text on your surface.
A state the faces don't name
ink overrides the status color with any CSS color, for a state outside the three, like a
stale reading. The mouth still follows health, so pick the face that fits, such as at_risk
for the flat one, and pass a label that names the real state, because the default name says
the health you passed.
<HealthFace health="at_risk" ink="var(--sys-fg-muted)" label="Health stale" />A change of ink rides the same spring as a change of health, and under reduced motion it
jumps.
Motion
One story: health to health on the shared spring (stiffness 420 / damping 40 /
mass 0.9), the same one Priority bars and
Work mark use.
| Rule | What happens |
|---|---|
| Mouth | Ends at x 88 and 168 on the 256 grid, a 16-unit round stroke knocked out of the face. The bow goes from +12 through flat to -12. |
| Eyes | Phosphor's, fixed. |
| Color | The categorical green and amber, and --destructive, or ink when you pass one. Rides the same spring. |
| Mount | Springs start on the current value, with no play-in. |
| Reduced motion | jump to the end state, and the color changes with no transition. |
| Interrupt | A new health retargets the same spring mid-flight. |
Props
Health face takes only these props, and it forwards its ref to the <svg>.
| Prop | Type | Default | What it does |
|---|---|---|---|
health | "on_track" | "at_risk" | "off_track" | Required. Sets the mouth, the ink, and the accessible name. | |
size | number | string | "1em" | The width and height. A number is pixels and a string is any CSS length. |
ink | string | Any CSS color. Replaces the status ink, for a state the three faces don't name. The mouth still follows health. | |
label | string | Health {state} | The accessible name and tooltip. "" hides the mark from assistive tech. |
className | string | Added to the <svg>, so size-4 sets the size from a class. |
Accessibility
- The root is an
<svg role="img">whose accessible name is built from the state, likeHealth On track, and it carries the same text in a<title>, which browsers show as a tooltip on hover.labelreplaces it, for a translated name. - When text beside the mark already says the state, the image name repeats it, so pass
label="", which makes the markaria-hiddenand drops the tooltip. A Chip trigger with a Health face as itsmarkand noaria-labelis read as "Health On track On track", so give the trigger one that names the property and the value. - Shape carries the state as well as color: a smile, a flat mouth, and a frown. In forced
colors the ink becomes
CanvasText,inkincluded, and the mouth, which is knocked out of the face, still shows. A state drawn withinkshares its mouth with one of the three, so itslabelor the text beside it is what tells them apart. Below 16px the mouth stroke is under a pixel, and the states are harder to tell apart. - 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 mouth and the ink jump to the new state with no transition.
When to use something else
- Badge for a status written as a word, in a role color. Pair Health face with Badge when the health needs both a shape and a label.
- Category colors for a value whose color the user picks and that keeps its hue in every look, like a select option.
- Priority bars for a priority 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 three-way state.
- Avatar for a person, since a face here means health and never identity.
Install
shadcn add @gesso/health-face