Skip to main content

Sentiment

Captures a qualitative user response using icons or scale indicators such as star ratings or emoji reactions. Use in feedback or survey flows where a structured emotional or satisfaction response is needed.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Container Required Wraps the entire control. Receives the fieldset role and associates the visible label via legend.
Legend / Label Required Describes the question being answered. Rendered as a <legend> inside a <fieldset> so all inputs share a single accessible label.
Scale item Required Each selectable unit — a star, emoji, or numeric indicator. Rendered as a visually styled radio input. Minimum one item; typical ranges are 3, 5, or 10 items.
Icon or glyph Required The visual representation of the scale item (star, face, number). Decorative when the radio label already conveys meaning; otherwise carries an aria-label.
Selected indicator Required Visual treatment applied to the chosen item and all lower-ranked items in cumulative scales (e.g. stars 1-3 fill when 3 is selected). Uses --op-color-interactive-default for fill.
Helper text Optional Supplementary guidance below the control, e.g. '1 = Very dissatisfied, 5 = Very satisfied'. Uses --op-text-sm and --op-color-text-secondary.
Error message Recommended Inline validation message displayed when the field is required but unanswered. Rendered with --op-color-status-error and the status-error icon.

Variants

States

State Behaviour
Default (unselected) All scale items render in their unfilled/outline state. No item carries aria-checked=true. The first item is programmatically focusable via tabindex=0; subsequent items are reachable with arrow keys.
Hovered The hovered item and, for cumulative variants (star), all preceding items preview the selected fill using --op-color-interactive-hover at reduced opacity. Cursor changes to pointer.
Focused The focused item receives a 3px focus ring using --op-color-interactive-focus. Visible regardless of input method. Hover preview does not activate on keyboard focus alone.
Selected The chosen item is filled with --op-color-interactive-default. For cumulative variants, all items at or below the selected value are also filled. aria-checked=true is set on the active radio.
Disabled All items render at 40% opacity. Pointer events are suppressed. aria-disabled=true is set on each input and the container fieldset. Previously selected value is preserved visually.
Read-only Renders the current value in its selected state without interactive affordances. Used in review/confirmation screens. No focus ring or hover states. aria-readonly=true on the group.
Error The container receives --op-color-status-error border. The error message is shown below the helper text. aria-describedby on the fieldset points to the error message id.

Usage guidelines

Do / Don't

Do

Always pair the sentiment control with a visible legend that clearly states what is being rated.

Don't

Do not rely on surrounding page context alone to explain the scale; screen readers read the fieldset legend in isolation.

Do

Label the ends of numeric and star scales (e.g. 'Not at all likely' / 'Extremely likely') so meaning is unambiguous.

Don't

Do not leave a 1-10 scale unlabelled — numbers carry different cultural assumptions in diverse government audiences.

Do

Mark the field as required in the form validation schema and display an error message when it is skipped.

Don't

Do not silently treat an unanswered sentiment field as a neutral midpoint in your data pipeline.

Do

Use the emoji variant for public-facing Social Point surveys targeting mixed-literacy audiences.

Don't

Do not use emoji in formal stakeholder reporting interfaces (Open Point SRM) where numeric precision is expected.

Layout & Spacing

Container: display flex, gap --op-space-8 (8px) between scale items. Legend: margin-bottom --op-space-8 (8px), font --op-text-sm, font-weight 600, color --op-color-text-primary. Scale item touch target: minimum 44x44px, achieved via padding --op-space-8 on each side. Icon/glyph size: 24x24px default; 20x20px in compact/inline contexts. Helper text: margin-top --op-space-4 (4px), font --op-text-xs, color --op-color-text-secondary. Error message: margin-top --op-space-4 (4px), font --op-text-xs, color --op-color-status-error. Gap between helper text and error message: --op-space-4 (4px).

Tokens

PartTokenValue
Selected icon fill --op-color-interactive-default Green-400 — used for filled stars, active emoji outline, and selected numeric button background.
Unselected icon --op-color-border-default Used for unfilled star outlines and inactive numeric button borders.
Hover preview fill --op-color-interactive-hover Applied at 60% opacity on hovered item and preceding items in cumulative variants.
Focus ring --op-color-interactive-focus 3px solid outline, 2px offset, applied to the focused scale item.
Legend text --op-color-text-primary Primary text colour for the question label.
Helper text --op-color-text-secondary Secondary text colour for scale endpoint labels and supplementary guidance.
Error message --op-color-status-error Applied to both the error icon and error message text.
Error container border --op-color-status-error 1px solid border on the fieldset container when in error state.
Disabled state --op-color-text-primary Rendered at 40% opacity via opacity property; do not substitute a lighter token.
Container background --op-color-bg-primary Transparent by default; inherits page background.

Engineering notes

  • Render the entire control as a <fieldset> with a <legend>. Do not use div + label as a substitute — the fieldset/legend pairing is required for screen readers to announce the group question with each option.
  • Each scale item is a visually hidden <input type='radio'> with a styled <label> wrapping or associated via for/id. The visual icon lives inside the label and is aria-hidden=true when the radio label text conveys full meaning.
  • For star and numeric variants, implement rover tabindex: only the selected item (or first item if none selected) holds tabindex=0. Arrow keys move focus and update selection; Tab exits the group.
  • Emit a custom change event with { name, value } payload so parent form libraries (e.g. React Hook Form, native FormData) can capture the rating without polling the DOM.
  • For the cumulative star variant, set the filled state on all items with index <= selectedIndex in the change handler. Do not rely on CSS sibling selectors for this in environments with Shadow DOM.
  • Persist the selected value to the form's data model on change, not on blur, so partially completed surveys can be saved as drafts.
  • Avoid dynamically inserting or removing scale items after initial render — screen readers may lose track of the group structure. If scale size must change, remount the entire fieldset.

Keyboard interaction

KeyAction
Tab Moves focus into the sentiment group (onto the selected item, or the first item if none selected). A second Tab exits the group to the next focusable element.
Arrow Right / Arrow Down Moves focus to the next scale item and selects it. Wraps from last item to first.
Arrow Left / Arrow Up Moves focus to the previous scale item and selects it. Wraps from first item to last.
Enter / Space Confirms selection of the currently focused item (equivalent to arrow-key selection for radio groups; included for user expectation).

Why it matters

Government surveys are frequently completed by people using assistive technologies, including older residents, people with disabilities, and public servants using managed IT environments with keyboard-only navigation. A properly constructed radio group ensures every user can submit a sentiment response without a mouse.

Focus

Only one item in the group is in the tab sequence at a time (rover tabindex pattern). The focused item displays a 3px focus ring using --op-color-interactive-focus with a 2px transparent offset to ensure visibility on both light and dark backgrounds. Focus does not move automatically on page load; it enters the group only when the user tabs to it. After form submission or error correction, focus is programmatically set to the first error field, which may be this component if it was left unanswered.

ARIA

Role or attributeWhen to useExample
role="radiogroup" (on fieldset, via implicit ARIA) Applied implicitly by the <fieldset> element. Explicitly add role=radiogroup only if using a non-fieldset container. <fieldset role="radiogroup" aria-labelledby="sentiment-legend" aria-required="true">
aria-required Set to true on the fieldset when the field is mandatory. Communicated to screen readers before the user enters the group. <fieldset aria-required="true">
aria-describedby Points to the id of the helper text element and/or the error message element so both are read after the legend. <fieldset aria-describedby="sentiment-helper sentiment-error">
aria-checked Set to true on the currently selected radio input. Managed automatically by the browser for native <input type=radio>; set explicitly for custom elements. <input type="radio" aria-checked="true" />
aria-disabled Set to true on each radio input and the fieldset when the control is disabled. <fieldset aria-disabled="true">
aria-invalid Set to true on the fieldset when validation fails and the error message is shown. <fieldset aria-invalid="true" aria-describedby="sentiment-error">

Contrast

Filled star / selected emoji icon: --op-color-interactive-default (green-400) against --op-color-bg-primary must meet 3:1 non-text contrast ratio (WCAG 1.4.11). Legend text: --op-color-text-primary against --op-color-bg-primary meets 4.5:1 for normal text at --op-text-sm. Helper text: --op-color-text-secondary against --op-color-bg-primary must be verified at --op-text-xs; if it falls below 4.5:1, increase font size to --op-text-sm or use --op-color-text-primary. Error text: --op-color-status-error against --op-color-bg-primary must meet 4.5:1. Do not use --op-color-status-error-bg as the sole error indicator — always include the text message. Focus ring: --op-color-interactive-focus at 3px provides sufficient contrast against both light and dark backgrounds by design.

Touch targets

Each scale item must have a minimum 44x44px tap target. For a 5-star control this means the total control width is at least 220px on mobile viewports. Use padding on the label rather than increasing icon size to maintain visual proportion. On narrow viewports (<375px), consider switching from a horizontal row to a 2-column grid layout.

Things to avoid

  • Do not convey sentiment meaning through colour alone (e.g. red-to-green gradient without numeric or text labels). Users who are colour blind cannot distinguish the scale.
  • Do not auto-advance to the next survey question when a sentiment item is selected — unexpected navigation confuses screen reader users and breaks the expected radio group interaction model.
  • Do not use animated transitions on the fill state without honouring prefers-reduced-motion: reduce. Rapid fill animations can trigger discomfort for users with vestibular disorders.
  • Do not remove the visible focus ring for aesthetic reasons. Government accessibility obligations require always-visible focus indicators.
  • Do not stack multiple sentiment controls inside a single fieldset — each question must have its own fieldset/legend pair.

Was this page helpful?

Updated 5 October 2026