Skip to content
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Getting startedWebReactSwiftUIJetpack ComposeFlutterReact NativePresetsYour own design systemUsing Tanrei with AI agentsBrandDesign tokensDesign systems and customizationTheme StudioColorTypographyIconsShapeElevationMotionInteraction statesBehaviourLayoutAccessibilityWordsColor tokensTypescale tokensShape tokensElevation tokensMotion tokensState tokensSpace and measure tokensComponentsButtonMain action buttonSegmented buttonButton groupButton with menuToolbarCopy buttonCheckboxRadio buttonSwitchChipMenuSliderSlider fieldCalendarDateTimePickerField groupRadio groupCheckbox groupSelectSelect fieldMenubarFile uploadFile picker AdvancedDrop zoneText input AdvancedText fieldChip fieldFieldCombobox AdvancedCombobox fieldNumber input AdvancedNumber fieldColor input AdvancedColor fieldOne-time-code input AdvancedOne-time-code fieldEmbedded text input AdvancedChat composerCardDividerModal layer AdvancedDialogListList rowBottom sheetSide sheetCarouselCode blockTablePopoverStepsAccordionDescription listFigureApp shellPage headerTree viewTimelineImage previewHandleResizable frameScroll areaBadgeProgress indicatorMeterNumber tickerLoading indicatorSnackbarTooltipKeyboard shortcutTagInline codeCalloutAvatarEmpty stateTag groupSkeletonFile itemSwatchMarquee textTabsTab viewApp barNavigation barNavigation drawerSearchLinkPaginationPrevious and next linksStepperBreadcrumbsTable of contentsLink cardFAB menuStatusHow Tanrei is builtContributingLicense and attribution
HomeFoundationsInteraction states

Interaction states

State layers, the gap-then-ring focus indicator, and how disabled is drawn.

content_copyView as Markdown

Interactive components share one vocabulary of states: enabled, hovered, focused, pressed, dragged, selected, activated, error, loading and disabled. A component’s spec lists the states it supports, and every platform must implement all of them.

State layers

Hover, focus, press and drag are shown with a state layer: a translucent overlay that covers the component’s container.

  • The layer takes the color of the content, that is the on- role of whatever it covers. On a filled button it is on-primary; on a text button it is the label color.
  • Its opacity comes from a token, not from the component.
  • Only one layer is visible at a time. When states overlap, the strongest wins: pressed over focused over hovered.
  • It fades in over a short effects transition and appears immediately on press.

The quiet selection is a layer too, and the one that stays: a list’s selected option, a menu’s current link item and the tonal tabs’ indicator are drawn in their content color at selected-state-layer-opacity, twice the hover layer, over whatever the item stands on. So a selected item stands as far from a hovered one as a hovered one stands from one at rest, on any surface. The hover, focus and press layers are laid over it, and the selection is never told by the fill alone: a check, a mark or the label says it too.

Token Value Specimen
--tanrei-sys-state-disabled-container-opacity 0.12 (12%) AaAa
--tanrei-sys-state-disabled-content-opacity 0.38 (38%) AaAa
--tanrei-sys-state-dragged-state-layer-opacity 0.16 (16%) AaAa
--tanrei-sys-state-focus-state-layer-opacity 0.1 (10%) AaAa
--tanrei-sys-state-hover-state-layer-opacity 0.08 (8%) AaAa
--tanrei-sys-state-pressed-state-layer-opacity 0.1 (10%) AaAa
--tanrei-sys-state-selected-state-layer-opacity 0.16 (16%) AaAa

Generated from design/tokens/tanrei.light.tokens.json, tokens/sys/state.tokens.json

Hover and press the buttons below to see the layer on different containers:

State layers

FilledTonalElevatedOutlinedText

Focus: gap, then ring

Keyboard focus is drawn as a 2px gap in the surface color followed by a 2px ring in the focus-ring role. The gap guarantees that the ring is visible even when the component’s own fill is close to the ring color, and the role is checked for 3:1 contrast against surfaces.

  • The ring is shown for keyboard and assistive-technology focus, not for pointer clicks.
  • It is never animated.
  • It follows the component’s shape, offset outward.

No ripple by default

Tanrei does not draw an expanding ripple on press. The pressed state layer is the feedback. A theme may opt into ripple, but no component depends on it.

Disabled

A disabled component draws its container at the disabled-container-opacity and its content at the disabled-content-opacity of on-surface, shows no state layer, and leaves the focus order. Disabled elements are exempt from contrast requirements, which is a reason to use them sparingly.

lightbulb

Tip

Prefer keeping an action enabled and explaining what is missing when it is used. A disabled button cannot tell anyone why it is disabled.