Skip to content

Commit 0d952fa

Browse files
userquinalexdln
andauthored
fix(a11y): improve toggle switches (#1782)
Co-authored-by: Vordgi <sasha2822222@gmail.com>
1 parent fee116c commit 0d952fa

File tree

2 files changed

+26
-148
lines changed

2 files changed

+26
-148
lines changed

app/components/Settings/Toggle.client.vue

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const id = useId()
2727
<template>
2828
<label
2929
:for="id"
30-
class="grid items-center gap-1.5 py-1 -my-1 grid-cols-[auto_1fr_auto]"
30+
class="grid items-center gap-1.5 py-1 -my-1 grid-cols-[auto_1fr_auto] cursor-pointer"
3131
:class="[justify === 'start' ? 'justify-start' : '']"
3232
:style="
3333
props.reverseOrder
@@ -39,9 +39,9 @@ const id = useId()
3939
<input
4040
role="switch"
4141
type="checkbox"
42-
:id
42+
:id="id"
4343
v-model="checked"
44-
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 bg-fg-subtle checked:bg-fg checked:border-fg focus-visible:(outline-2 outline-fg outline-offset-2) before:content-[''] before:absolute before:h-5 before:w-5 before:top-1px before:rounded-full before:bg-bg"
44+
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 bg-fg/50 transition-colors duration-200 ease-in-out checked:bg-fg hover:bg-fg/60 checked:hover:(bg-fg/80 after:opacity-50) focus-visible:(outline-2 outline-accent outline-offset-2) before:(content-[''] absolute h-5 w-5 top-px start-px rounded-full bg-bg transition-transform duration-200 ease-in-out) checked:before:translate-x-[20px] rtl:checked:before:-translate-x-[20px] after:(content-[''] absolute h-3.5 w-3.5 top-[4px] start-[4px] i-lucide:check bg-fg opacity-0 transition-all duration-200 ease-in-out) checked:after:opacity-100 checked:after:translate-x-[20px] rtl:checked:after:-translate-x-[20px]"
4545
style="grid-area: toggle"
4646
/>
4747
<TooltipApp
@@ -85,9 +85,9 @@ const id = useId()
8585
<input
8686
role="switch"
8787
type="checkbox"
88-
:id
88+
:id="id"
8989
v-model="checked"
90-
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 bg-fg-subtle checked:bg-fg checked:border-fg focus-visible:(outline-2 outline-fg outline-offset-2) before:content-[''] before:absolute before:h-5 before:w-5 before:top-1px before:rounded-full before:bg-bg"
90+
class="toggle appearance-none h-6 w-11 rounded-full border border-fg relative shrink-0 bg-fg/50 transition-colors duration-200 ease-in-out checked:bg-fg hover:bg-fg/60 checked:hover:(bg-fg/80 after:opacity-50) focus-visible:(outline-2 outline-accent outline-offset-2) before:(content-[''] absolute h-5 w-5 top-px start-px rounded-full bg-bg transition-transform duration-200 ease-in-out) checked:before:translate-x-[20px] rtl:checked:before:-translate-x-[20px] after:(content-[''] absolute h-3.5 w-3.5 top-[4px] start-[4px] i-lucide:check bg-fg opacity-0 transition-all duration-200 ease-in-out) checked:after:opacity-100 checked:after:translate-x-[20px] rtl:checked:after:-translate-x-[20px]"
9191
style="grid-area: toggle; justify-self: end"
9292
/>
9393
</template>
@@ -98,82 +98,37 @@ const id = useId()
9898
</template>
9999

100100
<style scoped>
101-
/* Thumb position: logical property for RTL support */
102-
.toggle::before {
103-
inset-inline-start: 1px;
104-
}
105-
106-
/* Track transition */
107-
.toggle {
108-
transition:
109-
background-color 200ms ease-in-out,
110-
border-color 100ms ease-in-out;
111-
}
112-
113-
.toggle::before {
114-
transition:
115-
background-color 200ms ease-in-out,
116-
translate 200ms ease-in-out;
117-
}
118-
119-
/* Hover states */
120-
.toggle:hover:not(:checked) {
121-
background: var(--fg-muted);
122-
}
123-
124-
.toggle:checked:hover {
125-
background: var(--fg-muted);
126-
border-color: var(--fg-muted);
127-
}
128-
129-
/* RTL-aware checked thumb position */
130-
:dir(ltr) .toggle:checked::before {
131-
translate: 20px;
132-
}
133-
134-
:dir(rtl) .toggle:checked::before {
135-
translate: -20px;
136-
}
137-
138-
@media (prefers-reduced-motion: reduce) {
139-
.toggle,
140-
.toggle::before {
141-
transition: none;
142-
}
143-
}
144-
145101
/* Support forced colors */
146102
@media (forced-colors: active) {
147-
label > span {
103+
.toggle {
148104
background: Canvas;
149-
color: Highlight;
150-
forced-color-adjust: none;
105+
border-color: CanvasText;
151106
}
152107
153-
label:has(.toggle:checked) > span {
108+
.toggle:checked {
154109
background: Highlight;
155-
color: Canvas;
110+
border-color: CanvasText;
156111
}
157112
158113
.toggle::before {
159-
forced-color-adjust: none;
160-
background-color: Highlight;
114+
background-color: CanvasText;
161115
}
162116
163-
.toggle,
164-
.toggle:hover {
165-
background: Canvas;
166-
border-color: CanvasText;
117+
.toggle:checked::before {
118+
background-color: Canvas;
167119
}
168120
169-
.toggle:checked,
170-
.toggle:checked:hover {
171-
background: Highlight;
172-
border-color: CanvasText;
121+
.toggle::after {
122+
background-color: Highlight;
173123
}
124+
}
174125
175-
.toggle:checked::before {
176-
background: Canvas;
126+
@media (prefers-reduced-motion: reduce) {
127+
.toggle,
128+
.toggle::before,
129+
.toggle::after {
130+
transition: none !important;
131+
animation: none !important;
177132
}
178133
}
179134
</style>

app/components/Settings/Toggle.server.vue

Lines changed: 5 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ const props = withDefaults(
44
label: string
55
description?: string
66
justify?: 'between' | 'start'
7+
tooltip?: string
8+
tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'
9+
tooltipTo?: string
10+
tooltipOffset?: number
711
reverseOrder?: boolean
812
}>(),
913
{
@@ -15,7 +19,7 @@ const props = withDefaults(
1519

1620
<template>
1721
<div
18-
class="grid items-center gap-4 py-1 -my-1 grid-cols-[auto_1fr_auto]"
22+
class="grid items-center gap-1.5 py-1 -my-1 grid-cols-[auto_1fr_auto]"
1923
:class="[justify === 'start' ? 'justify-start' : '']"
2024
:style="
2125
props.reverseOrder
@@ -51,84 +55,3 @@ const props = withDefaults(
5155
{{ description }}
5256
</p>
5357
</template>
54-
55-
<style scoped>
56-
/* Thumb position: logical property for RTL support */
57-
.toggle::before {
58-
inset-inline-start: 1px;
59-
}
60-
61-
/* Track transition */
62-
.toggle {
63-
transition:
64-
background-color 200ms ease-in-out,
65-
border-color 100ms ease-in-out;
66-
}
67-
68-
.toggle::before {
69-
transition:
70-
background-color 200ms ease-in-out,
71-
translate 200ms ease-in-out;
72-
}
73-
74-
/* Hover states */
75-
.toggle:hover:not(:checked) {
76-
background: var(--fg-muted);
77-
}
78-
79-
.toggle:checked:hover {
80-
background: var(--fg-muted);
81-
border-color: var(--fg-muted);
82-
}
83-
84-
/* RTL-aware checked thumb position */
85-
:dir(ltr) .toggle:checked::before {
86-
translate: 20px;
87-
}
88-
89-
:dir(rtl) .toggle:checked::before {
90-
translate: -20px;
91-
}
92-
93-
@media (prefers-reduced-motion: reduce) {
94-
.toggle,
95-
.toggle::before {
96-
transition: none;
97-
}
98-
}
99-
100-
/* Support forced colors */
101-
@media (forced-colors: active) {
102-
label > span {
103-
background: Canvas;
104-
color: Highlight;
105-
forced-color-adjust: none;
106-
}
107-
108-
label:has(.toggle:checked) > span {
109-
background: Highlight;
110-
color: Canvas;
111-
}
112-
113-
.toggle::before {
114-
forced-color-adjust: none;
115-
background-color: Highlight;
116-
}
117-
118-
.toggle,
119-
.toggle:hover {
120-
background: Canvas;
121-
border-color: CanvasText;
122-
}
123-
124-
.toggle:checked,
125-
.toggle:checked:hover {
126-
background: Highlight;
127-
border-color: CanvasText;
128-
}
129-
130-
.toggle:checked::before {
131-
background: Canvas;
132-
}
133-
}
134-
</style>

0 commit comments

Comments
 (0)