Navigation

v0.0.4
Forms

Scale

One answer on a row of numbers, from one end to the other.

On this page

A scale is one answer on a row of numbers, from one end to the other: how likely, how satisfied, how hard. Each number is a <label> around a native radio, so the group keeps the arrow keys and a form keeps the value. At rest a cell is a flat face on one contour; the chosen one is a mark of ink.

Scale

<div class="card w-full max-w(480)">  <fieldset class="fieldset">    <legend class="fieldset-legend">How likely are you to recommend Atlas to a colleague?</legend>    <div class="scale">      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="0">0</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="1">1</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="2">2</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="3">3</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="4">4</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="5">5</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="6">6</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="7">7</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="8" checked>8</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="9">9</label>      <label class="scale-option">        <input class="scale-input" type="radio" name="recommend" value="10">10</label>      <p class="scale-ends">        <span>Not at all likely</span>        <span>Extremely likely</span>      </p>    </div>  </fieldset></div>

#The row and its ends

The cells share the row's width, 4px apart, so five numbers and eleven both fill the column they are given and stay level with the fields around them: a cell is 38px tall, as a button is. A cell is never narrower than 28px; in a row too narrow for them all, the cells wrap onto a second line.

A number alone says nothing. scale-ends is a line under the row with two names, one for each end. Put it inside the scale after the last cell. The question itself belongs in a fieldset's legend, as above, or in an aria-label on a scale with role="radiogroup".

Five points

<div class="scale w-full max-w(280)" role="radiogroup" aria-label="How hard was it to set up?">  <label class="scale-option">    <input class="scale-input" type="radio" name="effort" value="1">1</label>  <label class="scale-option">    <input class="scale-input" type="radio" name="effort" value="2" checked>2</label>  <label class="scale-option">    <input class="scale-input" type="radio" name="effort" value="3">3</label>  <label class="scale-option">    <input class="scale-input" type="radio" name="effort" value="4">4</label>  <label class="scale-option">    <input class="scale-input" type="radio" name="effort" value="5">5</label>  <p class="scale-ends">    <span>Very easy</span>    <span>Very hard</span>  </p></div>

#Scale, segmented, rating or slider

A scale is a degree with no names in the middle. Two to five named options that switch a view are a segmented control. A score out of a few stars is a rating. An approximate value on a long range is a slider. A vote that shows how everyone answered is a poll.

#Accessibility

  • The radios share a name. The browser makes them one tab stop and moves between them with the arrow keys.
  • The number in the cell is the radio's label. The ends are not attached to any one cell, so say what the numbers mean in the question: "from 0, not at all likely, to 10, extremely likely".
  • The focus ring is drawn around the cell. On a touch screen a cell is 44px tall; eleven cells need about 320px of width to stay comfortable to press.
  • A disabled scale keeps its chosen cell visible in a quieter tone, so an answer already given can still be read.

#Classes

ClassDescription
scaleThe row: cells that share its width, 4px apart, on a fieldset or an element with role="radiogroup"
scale-optionOne number, on a label: a flat face 38px tall and never narrower than 28px, so a row too narrow for every cell wraps; pressed, it sinks into a well, and chosen, it is a mark of ink
scale-inputThe native radio inside the cell. It covers the cell and is not drawn
scale-endsA line under the row with two names, one for each end

#Custom properties

PropertyDefaultDescription
--scale-gap.25remSpace between cells

#Source

@master/ui/components/scale.css
/* * Scale: one answer on a row of numbers, from one end to the other. * How likely, how satisfied, how much: each number is a <label> around a native radio, so the group keeps the * arrow keys and the form keeps the value. The cells share the row's width. At rest a cell is a flat face on * one contour; the chosen one is a mark of ink, as a chosen day of a calendar is. * scale-ends is a line under the row that names its two ends, since a number alone says nothing. * Geometry: cells are 38px tall, as a button is (44 on a touch screen), 4px apart, with a small control's corner. */@layer components {  .scale {    --scale-gap: .25rem;    display: flex;    flex-wrap: wrap;    gap: var(--scale-gap);    min-width: 0;    margin: 0;    padding: 0;    border: 0;  }  /* One number. */  .scale-option {    position: relative;    display: grid;    flex: 1 1 0;    place-items: center;    /* A cell is never narrower than 28px: in a row too narrow for them all, the cells wrap. */    min-width: 1.75rem;    height: 2.375rem;    border-radius: calc(var(--radius-control-sm) * var(--corner-scale));    corner-shape: var(--corner-shape);    background: var(--color-surface-raised);    color: var(--color-text-body);    box-shadow: var(--shadow-flat);    font: var(--font-weight-control) .84375rem/1 var(--font-family-sans);    font-feature-settings: inherit;    font-variant-numeric: tabular-nums;    cursor: pointer;    touch-action: manipulation;    user-select: none;    -webkit-user-select: none;    -webkit-tap-highlight-color: transparent;    transition: box-shadow var(--duration-quick) var(--easing-glide);    @media (hover: hover) {      &:hover:not(:has(:checked, :disabled)) {        color: var(--color-text-strong);        box-shadow: 0 0 0 .5px var(--color-line-mark);      }    }    /* Pressed: the cell sinks into a well before it turns to ink. */    &:active:not(:has(:checked, :disabled)) {      background: var(--color-surface-inset);      box-shadow: var(--shadow-pressed);      transition-duration: var(--duration-press);    }    /* Chosen: ink. The face swaps at once; only its shadow eases. */    &:has(.scale-input:checked) {      background: var(--color-surface-inverse);      color: var(--color-text-inverse);      box-shadow: var(--shadow-ink-mark);    }    &:has(.scale-input:focus-visible) {      @apply --focus-ring;    }    &:has(.scale-input:disabled) {      background: var(--color-surface-inset);      color: var(--color-text-disabled);      cursor: not-allowed;    }    &:has(.scale-input:checked:disabled) {      background: var(--color-text-disabled);      color: var(--color-surface-raised);      box-shadow: none;    }    @media (pointer: coarse) {      height: 2.75rem;    }    @media (forced-colors: active) {      outline: 1px solid CanvasText;      outline-offset: -1px;      &:has(.scale-input:checked) {        background: Highlight;        color: HighlightText;        forced-color-adjust: none;      }    }  }  /* The native radio covers its cell and is not drawn: the cell is its face. */  .scale-input {    position: absolute;    inset: 0;    width: 100%;    height: 100%;    margin: 0;    opacity: 0;    appearance: none;    cursor: inherit;  }  /* What the two ends mean: a line under the row, one name at each end. */  .scale-ends {    display: flex;    flex: none;    justify-content: space-between;    gap: 1rem;    order: 1;    width: 100%;    margin: .125rem 0 0;    color: var(--color-text-muted);    font-size: .78125rem;    line-height: 1.25rem;  }}