Button States

dembrandt/dembrandt-skills/skills/button-states

作者 dembrandt20de5f225ea7cffe2a721ac18c1077a92769a013無授權條款收錄於 2026年10月9日更新於 2026年10月9日

Rest, hover, active, focus, disabled and loading states, derived from the base colour. Use when designing buttons, links, inputs or any clickable component.

僅含說明Design & Creative
AI 產生的概覽

定義按鈕與可點擊元件的六種互動狀態,並提供各狀態的 CSS 寫法。

功能
此技能規範按鈕、連結、輸入框及其他可點擊元件的靜止、懸停、按下、聚焦、停用與載入六種狀態。它說明如何透過調整 HSL 亮度從基礎色推導懸停色與按下色,並提供主要按鈕、次要按鈕、聚焦、停用與載入狀態的 CSS 範例。此外還包含選用的按下縮放效果,以及用來檢查各狀態是否齊全的審查清單。
適用情境
適用於設計或撰寫按鈕及其他互動元素的樣式,或需要加入懸停、按下、聚焦、停用、載入行為時。也可用於審查現有元件集,確認每種互動狀態都已定義且兼顧無障礙。
執行需求
無需指令碼或套件,僅為說明文件。CSS 範例假定專案中已定義 --color-primary、--color-border 等自訂屬性。

Button and Interactive Element States

Every interactive component must have a complete, visually distinct state for each interaction mode. Missing or ambiguous states make the UI feel unfinished and reduce user confidence.

The Six States

StateTriggerVisual signal
RestDefaultBase colour, cursor: pointer
HoverMouse overSlightly darker, subtle background shift
Active / PressedMouse down / tapNoticeably darker, slight scale-down
FocusKeyboard navigationVisible focus ring, no change to fill
DisabledNot availableLow contrast, cursor: not-allowed, no interaction
LoadingAsync action in progressSpinner or pulse, non-interactive

Deriving State Colours Algorithmically

State colours are not chosen independently — they are derived from the base colour by adjusting lightness in HSL. This guarantees coherence across the entire palette.

base:     hsl(H, S%, L%)hover:    hsl(H, S%, L% - 8%)    ← darken 8%active:   hsl(H, S%, L% - 14%)   ← darken 14%

Example: primary button #635BFF (hsl 243, 100%, 68%)

css
.btn-primary {  background: hsl(243, 100%, 68%);       /* rest    #635BFF */}.btn-primary:hover {  background: hsl(243, 100%, 60%);       /* hover   #4A40FF */}.btn-primary:active {  background: hsl(243, 100%, 54%);       /* active  #3429FF */}

For light buttons on dark backgrounds, invert the logic — lighten on hover instead of darkening.

Secondary / outlined buttons

css
.btn-secondary {  background: transparent;  border: 1px solid var(--color-border);  color: var(--color-text);}.btn-secondary:hover {  background: var(--color-grey-100);     /* subtle fill */  border-color: var(--color-grey-300);}.btn-secondary:active {  background: var(--color-grey-200);}

Focus State

Focus is a keyboard navigation requirement (WCAG 2.2). It must be visible and must not rely on the hover style alone — keyboard users do not trigger hover.

css
.btn:focus-visible {  outline: 2px solid var(--color-primary);  outline-offset: 3px;}
  • Use outline, not box-shadow, for focus rings — outline respects border-radius in modern browsers and does not affect layout
  • outline-offset: 2–4px gives the ring breathing room from the component edge
  • Never use outline: none without a replacement focus style

Disabled State

css
.btn:disabled,.btn[aria-disabled="true"] {  opacity: 0.4;  cursor: not-allowed;  pointer-events: none;}
  • Disabled elements are exempt from WCAG contrast requirements — low opacity is correct and intentional
  • Use pointer-events: none to prevent click events even if JS is bypassed
  • Do not change the shape or size of a disabled button — only colour and cursor change

Loading State

When a button triggers an async action, replace the label with a spinner and prevent re-submission.

css
.btn--loading {  pointer-events: none;  opacity: 0.7;}

The cursor does not change while loading. It signals affordance, not progress: the arrow for ordinary content, pointer for something you can act on. Progress belongs to the element and the loading region, which the user is already looking at, rather than to wherever the mouse happens to rest. cursor: wait is also inert next to pointer-events: none, so the rule reads as deliberate while doing nothing.

  • Keep the button width stable during loading — avoid layout shift when label is replaced by spinner
  • Return to rest state on completion (success or error)
  • For long-running operations, pair with a status message — a spinner alone does not tell the user what is happening

Scale on Active (Optional)

A subtle scale-down on press adds physical feedback — borrowed from Disney's squash principle.

css
.btn:active {  transform: scale(0.97);  transition: transform 80ms ease-out;}

Keep the scale value between 0.95–0.98. Below 0.95 feels like the button is breaking.

Complete Button CSS Reference

css
.btn {  cursor: pointer;  background: var(--color-primary);  color: white;  border-radius: var(--radius-button);  padding: var(--component-padding-y-md) var(--component-padding-x-md);  height: var(--component-height-md);  border: none;  transition: background 120ms ease-out, transform 80ms ease-out;}
.btn:hover           { background: var(--color-primary-hover); }.btn:active          { background: var(--color-primary-active); transform: scale(0.97); }.btn:focus-visible   { outline: 2px solid var(--color-primary); outline-offset: 3px; }.btn:disabled        { opacity: 0.4; cursor: not-allowed; pointer-events: none; }.btn.btn--loading    { opacity: 0.7; pointer-events: none; }

Review Checklist

  • Does every interactive element have all six states defined?
  • Are hover and active colours derived from the base by lightness adjustment (not chosen arbitrarily)?
  • Is focus state visible and using outline (not removed)?
  • Is disabled state low-opacity with cursor: not-allowed?
  • Does loading state prevent re-submission?
  • Are transition durations 80–150ms — not instant, not slow?
  • Does cursor: pointer appear on all interactive elements at rest?
  • Does the cursor stay unchanged while an action runs, with progress shown by the element itself?

來源與署名

來源:dembrandt/dembrandt-skills位於skills/button-states提交20de5f2

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架