Skip to content

Linear scale

Picks one number from a row, like 0 to 10, with words under the two ends. For stars or hearts, use Rating.

How likely are you to recommend us to a colleague?
Not at all likelyExtremely likely

Installation

pnpm dlx shadcn@latest add @wisty/linear-scale

The CLI installs the Radix or Base UI file to match your project.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • People answer a survey question with one number on a scale, like a Net Promoter Score from 0 to 10 or a satisfaction score from 1 to 5.
  • Every number should stay in view, with words under the two ends that say what they mean, like "Not at all likely" and "Extremely likely".
  • Ranges of the scale mean something, like detractors, passives and promoters. Color them with bands.

Don't use it

  • People rate something with stars, hearts or thumbs. Use Rating.
  • The options are words, not numbers. Use Radio group, or Toggle group for a segmented control in a toolbar.
  • The value is any number in a wide range, like a discount. Use Slider or a number input.
  • The score is shown, not entered. Use Gauge.

Usage

import {
  LinearScale,
  LinearScaleLabel,
  LinearScaleLabels,
} from "@/components/wisty/linear-scale"
LinearScale
└── LinearScaleLabels
    └── LinearScaleLabel

LinearScale draws one number for every whole number from min to max (0 to 10 by default). Put the end labels inside it: the first LinearScaleLabel sits under the first number and the last under the last number. Leave LinearScaleLabels out for a scale without words.

<LinearScale min={1} max={5} aria-labelledby="csat-legend">
  <LinearScaleLabels>
    <LinearScaleLabel>Very dissatisfied</LinearScaleLabel>
    <LinearScaleLabel>Very satisfied</LinearScaleLabel>
  </LinearScaleLabels>
</LinearScale>

The value is the chosen number as a string, like "9", and the same string submits with a form under name. Choosing the chosen number again keeps it. To clear the answer, control value and set it to null, as Rating's Clear example does.

Each band is { from, to, tone }, both ends included, with tone one of "error", "warning" or "success":

<LinearScale
  bands={[
    { from: 0, to: 6, tone: "error" },
    { from: 7, to: 8, tone: "warning" },
    { from: 9, to: 10, tone: "success" },
  ]}
/>

Examples

Score bands

The same question with `bands`: 0 to 6 in the error color, 7 and 8 in the warning color, 9 and 10 in the success color.

How likely are you to recommend us to a colleague?
Not at all likelyExtremely likely

One to five

A satisfaction question from 1 to 5, set with `min` and `max`.

How satisfied are you with the support you received?
Very dissatisfiedVery satisfied

States

A required question showing its error until a number is chosen, and a `disabled` scale with its answer and bands.

*How easy was it to get your issue resolved?
Very difficultVery easy
How likely are you to recommend us to a colleague?
Not at all likelyExtremely likely

API reference

PartBuilt onDescription
LinearScale<div> (opens in a new tab)A row of whole numbers to choose one from, like a 0 to 10 likelihood score.
LinearScaleLabels<div> (opens in a new tab)
LinearScaleLabel<span> (opens in a new tab)

LinearScale

A row of whole numbers to choose one from, like a 0 to 10 likelihood score.

PropTypeDefaultDescription
minnumber0The first number on the scale, a whole number.
maxnumber10The last number on the scale, a whole number, no lower than min.
bandsreadonly LinearScaleBand[][]Colored ranges of numbers. Numbers outside every band keep the plain look.
valuestring | nullThe chosen number as a string, controlled. null for none.
defaultValuestringThe number chosen at first, uncontrolled.
onValueChange(value: string) => voidCalled with the chosen number as a string.
namestringThe radios' name, to submit the choice with a form. Generated when not given.
disabledbooleanfalseDisables every number.
requiredbooleanfalseA number must be chosen before the form submits.
formstringThe id of a form the radios belong to, outside it.

LinearScaleBandtype

MemberTypeDefaultDescription
from*numberThe band's first number, included.
to*numberThe band's last number, included.
tone*LinearScaleToneThe band's color, as a strip over each of its numbers.

SLDS mapping

LinearScale is slds-radio_button-group, and each number is an slds-radio_button: a native radio, visually hidden, and its slds-radio_button__label. The end labels and the band strips have no SLDS class.

Known gaps:

  • The numbers have narrower padding than SLDS's radio buttons, so 0 to 10 fits a form column.
  • SLDS has no rating scale or band. The strip over a banded number is drawn with the error, warning and success colors.
PropValueSLDSNotes
bandserror, warning, success (added)NoneA colored strip over the top of each number in a band.
disabledtrue (added)slds-radio_button [type=radio][disabled]Every number turns gray, and a band's strip with it.

Accessibility

  • Name the scale with aria-labelledby pointing at a FieldLegend, or with aria-label. Each radio is named by its number.
  • The numbers are one Tab stop. Tab enters at the chosen number, or the first one when none is chosen, and the arrow keys move the choice.
  • The end labels become the scale's description, after any aria-describedby you pass, so a screen reader reads what the ends mean when focus enters the scale.
  • For an error, set aria-invalid on LinearScale, which marks the group, and point aria-describedby at the FieldError. With required, the radios are also invalid to the browser until a number is chosen.
  • A band's color is a hint, not the answer. The number carries the meaning, so the bands need no text of their own.

Responsive behavior

  • The numbers keep their width and never wrap. When the row is wider than its container, the scale scrolls sideways, and focusing a number scrolls it into view.
  • The scale takes the width of its container. Inside a container that sizes to its content, give that container a width, like w-full max-w-md on the FieldSet.

Rules

  • min, max and every band's ends are whole numbers, and bands stay inside the range without overlapping. Anything else throws.
  • Keep the end labels short. Each one wraps within half the scale's width.
  • Never pass asChild or render.