{"id":"a656967b-98a4-4078-a1c2-dcde4641ccf8","shortId":"M9cwav","kind":"skill","title":"button-states","tagline":"Every interactive element needs a complete set of visual states — rest, hover, active/pressed, focus, disabled, and loading. States should be derived algorithmically from the base colour, not chosen arbitrarily. Use when designing buttons, links, inputs, or any clickable componen","description":"# Button and Interactive Element States\n\nEvery 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.\n\n## The Six States\n\n| State | Trigger | Visual signal |\n|---|---|---|\n| **Rest** | Default | Base colour, cursor: pointer |\n| **Hover** | Mouse over | Slightly darker, subtle background shift |\n| **Active / Pressed** | Mouse down / tap | Noticeably darker, slight scale-down |\n| **Focus** | Keyboard navigation | Visible focus ring, no change to fill |\n| **Disabled** | Not available | Low contrast, cursor: not-allowed, no interaction |\n| **Loading** | Async action in progress | Spinner or pulse, non-interactive |\n\n## Deriving State Colours Algorithmically\n\nState colours are not chosen independently — they are derived from the base colour by adjusting lightness in HSL. This guarantees coherence across the entire palette.\n\n```\nbase:     hsl(H, S%, L%)\nhover:    hsl(H, S%, L% - 8%)    ← darken 8%\nactive:   hsl(H, S%, L% - 14%)   ← darken 14%\n```\n\n### Example: primary button `#635BFF` (hsl 243, 100%, 68%)\n\n```css\n.btn-primary {\n  background: hsl(243, 100%, 68%);       /* rest    #635BFF */\n}\n.btn-primary:hover {\n  background: hsl(243, 100%, 60%);       /* hover   #4A40FF */\n}\n.btn-primary:active {\n  background: hsl(243, 100%, 54%);       /* active  #3429FF */\n}\n```\n\nFor light buttons on dark backgrounds, invert the logic — lighten on hover instead of darkening.\n\n### Secondary / outlined buttons\n\n```css\n.btn-secondary {\n  background: transparent;\n  border: 1px solid var(--color-border);\n  color: var(--color-text);\n}\n.btn-secondary:hover {\n  background: var(--color-grey-100);     /* subtle fill */\n  border-color: var(--color-grey-300);\n}\n.btn-secondary:active {\n  background: var(--color-grey-200);\n}\n```\n\n## Focus State\n\nFocus 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.\n\n```css\n.btn:focus-visible {\n  outline: 2px solid var(--color-primary);\n  outline-offset: 3px;\n}\n```\n\n- Use `outline`, not `box-shadow`, for focus rings — `outline` respects `border-radius` in modern browsers and does not affect layout\n- `outline-offset: 2–4px` gives the ring breathing room from the component edge\n- Never use `outline: none` without a replacement focus style\n\n## Disabled State\n\n```css\n.btn:disabled,\n.btn[aria-disabled=\"true\"] {\n  opacity: 0.4;\n  cursor: not-allowed;\n  pointer-events: none;\n}\n```\n\n- Disabled elements are exempt from WCAG contrast requirements — low opacity is correct and intentional\n- Use `pointer-events: none` to prevent click events even if JS is bypassed\n- Do not change the shape or size of a disabled button — only colour and cursor change\n\n## Loading State\n\nWhen a button triggers an async action, replace the label with a spinner and prevent re-submission.\n\n```css\n.btn--loading {\n  pointer-events: none;\n  cursor: wait;\n  opacity: 0.7;\n}\n```\n\n- Keep the button width stable during loading — avoid layout shift when label is replaced by spinner\n- Return to rest state on completion (success or error)\n- For long-running operations, pair with a status message — a spinner alone does not tell the user what is happening\n\n## Scale on Active (Optional)\n\nA subtle scale-down on press adds physical feedback — borrowed from Disney's squash principle.\n\n```css\n.btn:active {\n  transform: scale(0.97);\n  transition: transform 80ms ease-out;\n}\n```\n\nKeep the scale value between `0.95–0.98`. Below `0.95` feels like the button is breaking.\n\n## Complete Button CSS Reference\n\n```css\n.btn {\n  cursor: pointer;\n  background: var(--color-primary);\n  color: white;\n  border-radius: var(--radius-button);\n  padding: var(--component-padding-y-md) var(--component-padding-x-md);\n  height: var(--component-height-md);\n  border: none;\n  transition: background 120ms ease-out, transform 80ms ease-out;\n}\n\n.btn:hover           { background: var(--color-primary-hover); }\n.btn:active          { background: var(--color-primary-active); transform: scale(0.97); }\n.btn:focus-visible   { outline: 2px solid var(--color-primary); outline-offset: 3px; }\n.btn:disabled        { opacity: 0.4; cursor: not-allowed; pointer-events: none; }\n.btn.btn--loading    { opacity: 0.7; cursor: wait; pointer-events: none; }\n```\n\n## Review Checklist\n\n- [ ] Does every interactive element have all six states defined?\n- [ ] Are hover and active colours derived from the base by lightness adjustment (not chosen arbitrarily)?\n- [ ] Is focus state visible and using `outline` (not removed)?\n- [ ] Is disabled state low-opacity with `cursor: not-allowed`?\n- [ ] Does loading state prevent re-submission?\n- [ ] Are transition durations 80–150ms — not instant, not slow?\n- [ ] Does `cursor: pointer` appear on all interactive elements at rest?","tags":["button","states","dembrandt","skills","accessibility","agent-skills","claude-code-skills","claude-skills","cursor-skills","design-system","design-tokens","enterprise-ux"],"capabilities":["skill","source-dembrandt","skill-button-states","topic-accessibility","topic-agent-skills","topic-claude-code-skills","topic-claude-skills","topic-cursor-skills","topic-design-system","topic-design-tokens","topic-enterprise-ux","topic-gestalt","topic-skills-sh","topic-typography","topic-ui-design"],"categories":["dembrandt-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dembrandt/dembrandt-skills/button-states","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dembrandt/dembrandt-skills","source_repo":"https://github.com/dembrandt/dembrandt-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 9 github stars · SKILL.md body (4,864 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:08:26.247Z","embedding":null,"createdAt":"2026-05-18T13:14:00.478Z","updatedAt":"2026-05-18T19:08:26.247Z","lastSeenAt":"2026-05-18T19:08:26.247Z","tsv":"'0.4':397,665 '0.7':480,677 '0.95':564,567 '0.97':552,646 '0.98':565 '100':195,204,215,226,275 '120ms':619 '14':186,188 '150ms':741 '1px':255 '2':366 '2.2':305 '200':295 '243':194,203,214,225 '2px':331,652 '300':285 '3429ff':229 '3px':340,661 '4a40ff':218 '4px':367 '54':227 '60':216 '635bff':192,207 '68':196,205 '8':178,180 '80':740 '80ms':555,624 'across':164 'action':130,458 'activ':96,181,222,228,289,529,549,637,643,698 'active/pressed':16 'add':538 'adjust':157,706 'affect':361 'algorithm':25,142 'allow':125,401,669,729 'alon':318,518 'ambigu':64 'appear':749 'arbitrarili':32,709 'aria':393 'aria-dis':392 'async':129,457 'avail':119 'avoid':488 'background':94,201,212,223,235,252,270,290,582,618,630,638 'base':28,84,154,168,703 'border':254,260,279,353,590,615 'border-color':278 'border-radius':352,589 'borrow':541 'box':345 'box-shadow':344 'break':573 'breath':371 'browser':357 'btn':199,209,220,250,267,287,326,389,391,471,548,579,628,636,647,662 'btn-primari':198,208,219 'btn-secondari':249,266,286 'btn.btn':674 'button':2,36,43,191,232,247,444,454,483,571,575,595 'button-st':1 'bypass':433 'chang':114,436,449 'checklist':685 'chosen':31,147,708 'click':427 'clickabl':41 'coher':163 'color':259,261,264,273,280,283,293,335,585,587,633,641,656 'color-bord':258 'color-grey':272,282,292 'color-primari':334,584,655 'color-primary-act':640 'color-primary-hov':632 'color-text':263 'colour':29,85,141,144,155,446,699 'complet':9,54,502,574 'compon':50,375,599,605,612 'componen':42 'component-height-md':611 'component-padding-x-md':604 'component-padding-y-md':598 'confid':74 'contrast':121,412 'correct':417 'css':197,248,325,388,470,547,576,578 'cursor':86,122,398,448,477,580,666,678,726,747 'dark':234 'darken':179,187,244 'darker':92,102 'default':83 'defin':694 'deriv':24,139,151,700 'design':35 'disabl':18,117,386,390,394,406,443,663,720 'disney':543 'distinct':56 'durat':739 'eas':557,621,626 'ease-out':556,620,625 'edg':376 'element':6,46,407,689,753 'entir':166 'error':505 'even':429 'event':404,423,428,475,672,682 'everi':4,48,687 'exampl':189 'exempt':409 'feedback':540 'feel':69,568 'fill':116,277 'focus':17,107,111,296,298,328,348,384,649,711 'focus-vis':327,648 'give':368 'grey':274,284,294 'guarante':162 'h':170,175,183 'happen':526 'height':609,613 'hover':15,88,173,211,217,241,269,316,324,629,635,696 'hsl':160,169,174,182,193,202,213,224 'independ':148 'input':38 'instant':743 'instead':242 'intent':419 'interact':5,45,49,60,127,138,688,752 'invert':236 'js':431 'keep':481,559 'keyboard':108,301,319 'l':172,177,185 'label':461,492 'layout':362,489 'light':158,231,705 'lighten':239 'like':569 'link':37 'load':20,128,450,472,487,675,731 'logic':238 'long':508 'long-run':507 'low':120,414,723 'low-opac':722 'make':66 'md':602,608,614 'messag':515 'miss':62 'mode':61 'modern':356 'mous':89,98 'must':51,307,311 'navig':109,302 'need':7 'never':377 'non':137 'non-interact':136 'none':380,405,424,476,616,673,683 'not-allow':123,399,667,727 'notic':101 'offset':339,365,660 'opac':396,415,479,664,676,724 'oper':510 'option':530 'outlin':246,330,338,342,350,364,379,651,659,716 'outline-offset':337,363,658 'pad':596,600,606 'pair':511 'palett':167 'physic':539 'pointer':87,403,422,474,581,671,681,748 'pointer-ev':402,421,473,670,680 'press':97,537 'prevent':426,466,733 'primari':190,200,210,221,336,586,634,642,657 'principl':546 'progress':132 'puls':135 'radius':354,591,594 'radius-button':593 're':468,735 're-submiss':467,734 'reduc':72 'refer':577 'reli':313 'remov':718 'replac':383,459,494 'requir':303,413 'respect':351 'rest':14,82,206,499,755 'return':497 'review':684 'ring':112,349,370 'room':372 'run':509 'scale':105,527,534,551,561,645 'scale-down':104,533 'secondari':245,251,268,288 'set':10 'shadow':346 'shape':438 'shift':95,490 'signal':81 'six':76,692 'size':440 'skill' 'skill-button-states' 'slight':91,103 'slow':745 'solid':256,332,653 'source-dembrandt' 'spinner':133,464,496,517 'squash':545 'stabl':485 'state':3,13,21,47,57,65,77,78,140,143,297,387,451,500,693,712,721,732 'status':514 'style':317,385 'submiss':469,736 'subtl':93,276,532 'success':503 'tap':100 'tell':521 'text':265 'topic-accessibility' 'topic-agent-skills' 'topic-claude-code-skills' 'topic-claude-skills' 'topic-cursor-skills' 'topic-design-system' 'topic-design-tokens' 'topic-enterprise-ux' 'topic-gestalt' 'topic-skills-sh' 'topic-typography' 'topic-ui-design' 'transform':550,554,623,644 'transit':553,617,738 'transpar':253 'trigger':79,323,455 'true':395 'ui':68 'unfinish':70 'use':33,341,378,420,715 'user':73,320,523 'valu':562 'var':257,262,271,281,291,333,583,592,597,603,610,631,639,654 'visibl':110,309,329,650,713 'visual':12,55,80 'wait':478,679 'wcag':304,411 'white':588 'width':484 'without':381 'x':607 'y':601","prices":[{"id":"4bc11755-eb04-47f4-9f8d-9857de40ef1d","listingId":"a656967b-98a4-4078-a1c2-dcde4641ccf8","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dembrandt","category":"dembrandt-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:00.478Z"}],"sources":[{"listingId":"a656967b-98a4-4078-a1c2-dcde4641ccf8","source":"github","sourceId":"dembrandt/dembrandt-skills/button-states","sourceUrl":"https://github.com/dembrandt/dembrandt-skills/tree/main/skills/button-states","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:00.478Z","lastSeenAt":"2026-05-18T19:08:26.247Z"}],"details":{"listingId":"a656967b-98a4-4078-a1c2-dcde4641ccf8","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dembrandt","slug":"button-states","github":{"repo":"dembrandt/dembrandt-skills","stars":9,"topics":["accessibility","agent-skills","claude-code-skills","claude-skills","cursor-skills","design-system","design-tokens","enterprise-ux","gestalt","skills-sh","typography","ui-design","ux","wcag"],"license":"mit","html_url":"https://github.com/dembrandt/dembrandt-skills","pushed_at":"2026-05-14T22:34:06Z","description":"UX and design system skills for AI agents based on 20 years of experience","skill_md_sha":"cedcd1b4366c344b268ea6c8bad6298fdf9cccdb","skill_md_path":"skills/button-states/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dembrandt/dembrandt-skills/tree/main/skills/button-states"},"layout":"multi","source":"github","category":"dembrandt-skills","frontmatter":{"name":"button-states","description":"Every interactive element needs a complete set of visual states — rest, hover, active/pressed, focus, disabled, and loading. States should be derived algorithmically from the base colour, not chosen arbitrarily. Use when designing buttons, links, inputs, or any clickable component."},"skills_sh_url":"https://skills.sh/dembrandt/dembrandt-skills/button-states"},"updatedAt":"2026-05-18T19:08:26.247Z"}}