Skip to content

feat(webkit): give Switch its own disabled state - #846

Open
gabriel-lisboa-azion wants to merge 2 commits into
mainfrom
feat/switch-disabled
Open

feat(webkit): give Switch its own disabled state#846
gabriel-lisboa-azion wants to merge 2 commits into
mainfrom
feat/switch-disabled

Conversation

@gabriel-lisboa-azion

Copy link
Copy Markdown
Collaborator

Summary

Switch gains a disabled prop, so the locked state is visible at every call site instead of only inside a wrapper.

The gap. Switch had no disabled prop — the disabled visual lived in FieldSwitch / FieldSwitchBlock, which faked it with opacity-50 (+ pointer-events-none) over a still-live control. That covers only the wrapped case. A bare Switch is legitimate wherever the label already sits outside the control — an ItemGroup row names the field with Item.Title — and there the consumer had no wrapper to own the visual: :disabled blocked interaction natively while the switch still looked interactive.

Now the control owns it.

  • Tokens, not an opacity wash: --bg-disabled track + --border-default border + --text-disabled handle + cursor-not-allowed. The disabled treatment wins over the checked tokens, so a locked switch reads the same in the off and on positions.
  • Native inertness: the root is <button disabled>, so it leaves the tab order and neither click nor Space / Enter emits update:modelValue (both handlers early-return too).
  • Mirrors: aria-disabled and data-disabled follow the prop.
  • Hover: the inset --bg-hover overlay is suppressed (data-[disabled]:hover:shadow-none) while the element stays hit-testable, so cursor-not-allowed is what the pointer actually shows.
  • FieldSwitch / FieldSwitchBlock drop their opacity + pointer-events-none hacks and simply forward disabled to the control.

Notes

  • No new dependencies. Not breaking — additive prop, default false, so existing call sites are unchanged (the two wrappers move from a visual hack to the real prop).
  • spec_version 9, .specs/switch.md checksum refreshed; the spec records why the earlier "Disabled is omitted" decision was reversed. catalog.json regenerated (switch only).
  • New Disabled story renders both positions; switch.test.ts grows to 28 passing cases, including a computed-style assertion that the locked track is identical off vs on and genuinely different from the enabled checked track, plus an axe pass on the disabled render.
  • Linux visual baselines are regenerated on this branch in a follow-up test(storybook) commit.

robsongajunior
robsongajunior previously approved these changes Aug 3, 2026
Switch had no `disabled` prop: the disabled visual was owned by the
FieldSwitch / FieldSwitchBlock wrappers, which faked it with an opacity wash
and pointer-events-none over the live control. That works only where a
wrapper exists — a bare Switch, used wherever the label already sits outside
the control (an ItemGroup row naming the field with Item.Title), had no
wrapper to defer to, so `:disabled` blocked interaction natively while the
control still looked live.

The control now owns the state. `disabled` renders the token surfaces
(--bg-disabled track, --text-disabled handle, cursor-not-allowed) rather than
an opacity wash, and wins over the checked tokens so a locked switch reads
the same in both positions. The root is natively <button disabled>, so it
leaves the tab order and neither click nor Space/Enter emits
update:modelValue; `aria-disabled` and `data-disabled` mirror the prop. The
hover overlay is suppressed while keeping the element hit-testable, so
cursor-not-allowed is what the pointer shows.

FieldSwitch and FieldSwitchBlock drop their opacity/pointer-events hacks and
forward `disabled` to the control.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants