Formsstable.scale
Scale input
A 1–N rating as a row of numbered buttons with words at both ends, e.g. sleep, mood or effort (RPE).
For quick self-ratings in the athlete app (wellness check-in, training load) and short feedback questions. Each step is a large tap target; the anchors at both ends say what the extremes mean ("schlecht" … "super").
Underneath it is a radio group: one value, arrow keys to change, submitted with the form. Each radio has an accessible name like "1 von 5, schlecht", so the anchors are only decorative for screen readers.
Use the same direction everywhere: low = bad / easy on the start side, high = good / hard on the end side. Don't pre-select a value unless the person already answered.
Examples
Wellness: sleep
Data
render({
"id": "sleep",
"legend": "Wie hast du geschlafen?",
"minLabel": "schlecht",
"maxLabel": "super",
"value": 4
})Markup
<fieldset class="fieldset">
<legend>Wie hast du geschlafen?</legend>
<div class="scale">
<span class="scale__anchor" aria-hidden="true">schlecht</span>
<div class="scale__options"><label><input type="radio" id="sleep-1" name="sleep" value="1" aria-label="1 von 5, schlecht"><span>1</span></label><label><input type="radio" id="sleep-2" name="sleep" value="2" aria-label="2 von 5"><span>2</span></label><label><input type="radio" id="sleep-3" name="sleep" value="3" aria-label="3 von 5"><span>3</span></label><label><input type="radio" id="sleep-4" name="sleep" value="4" aria-label="4 von 5" checked><span>4</span></label><label><input type="radio" id="sleep-5" name="sleep" value="5" aria-label="5 von 5, super"><span>5</span></label></div>
<span class="scale__anchor" aria-hidden="true">super</span>
</div>
</fieldset>Effort (RPE 1–10)
Data
render({
"id": "rpe",
"legend": "Wie anstrengend war das Training?",
"max": 10,
"minLabel": "sehr leicht",
"maxLabel": "maximal",
"required": true
})Markup
<fieldset class="fieldset">
<legend>Wie anstrengend war das Training?</legend>
<div class="scale">
<span class="scale__anchor" aria-hidden="true">sehr leicht</span>
<div class="scale__options" style="--scale-steps: 10"><label><input type="radio" id="rpe-1" name="rpe" value="1" aria-label="1 von 10, sehr leicht" required><span>1</span></label><label><input type="radio" id="rpe-2" name="rpe" value="2" aria-label="2 von 10" required><span>2</span></label><label><input type="radio" id="rpe-3" name="rpe" value="3" aria-label="3 von 10" required><span>3</span></label><label><input type="radio" id="rpe-4" name="rpe" value="4" aria-label="4 von 10" required><span>4</span></label><label><input type="radio" id="rpe-5" name="rpe" value="5" aria-label="5 von 10" required><span>5</span></label><label><input type="radio" id="rpe-6" name="rpe" value="6" aria-label="6 von 10" required><span>6</span></label><label><input type="radio" id="rpe-7" name="rpe" value="7" aria-label="7 von 10" required><span>7</span></label><label><input type="radio" id="rpe-8" name="rpe" value="8" aria-label="8 von 10" required><span>8</span></label><label><input type="radio" id="rpe-9" name="rpe" value="9" aria-label="9 von 10" required><span>9</span></label><label><input type="radio" id="rpe-10" name="rpe" value="10" aria-label="10 von 10, maximal" required><span>10</span></label></div>
<span class="scale__anchor" aria-hidden="true">maximal</span>
</div>
</fieldset>Data contract
What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Id prefix for the option ids {id}-1 … {id}-{max}. Must be unique on the page.pattern ^[A-Za-z][\w-]*$ |
legend | string | required | — | The question ("Wie hast du geschlafen?"). min 1 chars |
name | string | optional | — | Form field name; defaults to id. |
max | integer | optional | 5 | Number of steps; the scale runs from 1 to max.≥ 3 · ≤ 10 |
value | integer | optional | — | Selected step, if already answered. ≥ 1 · ≤ 10 |
minLabel | string | optional | — | Anchor word for 1 ("schlecht", "sehr leicht"). |
maxLabel | string | optional | — | Anchor word for max ("super", "maximal"). |
required | boolean | optional | false | An answer is required (native required). |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"id",
"legend"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][\\w-]*$",
"description": "Id prefix for the option ids `{id}-1` … `{id}-{max}`. Must be unique on the page."
},
"legend": {
"type": "string",
"minLength": 1,
"description": "The question (\"Wie hast du geschlafen?\")."
},
"name": {
"type": "string",
"description": "Form field name; defaults to `id`."
},
"max": {
"type": "integer",
"minimum": 3,
"maximum": 10,
"default": 5,
"description": "Number of steps; the scale runs from 1 to `max`."
},
"value": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"description": "Selected step, if already answered."
},
"minLabel": {
"type": "string",
"description": "Anchor word for 1 (\"schlecht\", \"sehr leicht\")."
},
"maxLabel": {
"type": "string",
"description": "Anchor word for `max` (\"super\", \"maximal\")."
},
"required": {
"type": "boolean",
"default": false,
"description": "An answer is required (native `required`)."
}
}
}Markup & states
Root: div (inside fieldset.fieldset).scale — usable without render() by writing the markup directly.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
style="--scale-steps: N" | integer | — | Number of columns (set by render when max ≠ 5). |
aria-label (on radios) | "{n} von {max}[, anchor]" | — | Accessible name per step. |
Parts
| Part | Description |
|---|---|
.scale__anchor | Anchor words at start and end (aria-hidden; the meaning is in the radio names). |
.scale__options | Grid of steps. |
.scale__options label > input + span | Transparent radio over the visible number. |
States
| State | Description |
|---|---|
input:checked + span | Accent fill. |
input:focus-visible + span | Focus ring. |
CSS custom properties
| Property | Default | Description |
|---|---|---|
--scale-steps | 5 | Number of grid columns in .scale__options. |
Accessibility
- Native radios in a
fieldsetwithlegend: the question is announced, arrow keys move between steps. - Each radio is named "3 von 5"; the end steps include the anchor word ("1 von 5, schlecht").
- Steps are at least 44 px high; on narrow phones scales above 7 steps get tight — prefer 5.
Guidance
Related: Checkbox / radio group · Segmented control