Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c498289
fix: settings toggle should use input
abbeyperini Feb 5, 2026
439a09e
fixes: animation, reacty code, and styling
abbeyperini Feb 9, 2026
a6a084f
fix: support rtl
abbeyperini Feb 10, 2026
0db4fda
Fix: use theme colors, toggle size
abbeyperini Feb 10, 2026
52963f3
Merge branch 'main' into fix/1028
abbeyperini Feb 10, 2026
19c31f2
fix: unneeded code, tests
abbeyperini Feb 10, 2026
4bcbce9
fix: use useID instead of adding label to "toggle"
abbeyperini Feb 10, 2026
98e4265
Merge branch 'main' into fix/1028
abbeyperini Feb 10, 2026
bac8f7b
fix: dot position
abbeyperini Feb 11, 2026
4182e9f
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
6528caa
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
e82dccd
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
a36bd55
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
ef96b93
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
df6a9d0
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
e1887e2
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
177d4cc
fix: css instead of js for dir, unneeded code
abbeyperini Feb 11, 2026
9d39717
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
d02f011
Update app/components/Settings/Toggle.client.vue
abbeyperini Feb 11, 2026
cfed8ca
chore: clean up slightly
danielroe Feb 11, 2026
96afb3c
chore: move into atomic css, respect `prefers-reduced-motion: reduce`…
danielroe Feb 11, 2026
e0836ba
Merge remote-tracking branch 'origin/main' into fix/1028
danielroe Feb 11, 2026
383cfc0
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 11, 2026
ee9b8d5
chore: remove class prop
danielroe Feb 11, 2026
ea8723f
chore: update server equivalent following merge
danielroe Feb 11, 2026
4bf2934
refactor: rewrite without extra element
danielroe Feb 11, 2026
ab07136
chore: remove cursor-pointer
danielroe Feb 11, 2026
837bc10
fix: forced contrast mode styling, server style tag
abbeyperini Feb 11, 2026
61a0040
fix: forced contrast colors are hard
abbeyperini Feb 11, 2026
a5cdb76
fix: force colors are really hard, for real
abbeyperini Feb 11, 2026
8c140aa
fix: animation flash
abbeyperini Feb 11, 2026
219ce0a
Merge branch 'main' into fix/1028
abbeyperini Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 100 additions & 60 deletions app/components/Settings/Toggle.client.vue
Original file line number Diff line number Diff line change
@@ -1,81 +1,121 @@
<script setup lang="ts">
defineProps<{
label?: string
const props = defineProps<{
label: string
description?: string
class?: string
}>()

defineEmits(['update:modelValue'])
Comment thread
danielroe marked this conversation as resolved.
Outdated
const checked = defineModel<boolean>({
default: false,
required: true,
})
const id = 'toggle-' + props.label
const { locale, locales } = useI18n()
const dir = computed(() => {
const localeObj = locales.value.find(item => item.code === locale.value)
return localeObj?.dir ?? 'ltr'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
knowler marked this conversation as resolved.
Outdated
})
</script>

<template>
<button
type="button"
class="w-full flex items-center justify-between gap-4 group focus-visible:outline-none py-1 -my-1"
role="switch"
:aria-checked="checked"
@click="checked = !checked"
:class="class"
>
<span v-if="label" class="text-sm text-fg font-medium text-start">
{{ label }}
</span>
<span
class="inline-flex items-center h-6 w-11 shrink-0 rounded-full border p-0.25 transition-colors duration-200 shadow-sm ease-in-out motion-reduce:transition-none group-focus-visible:(outline-accent/70 outline-offset-2 outline-solid)"
:class="
checked
? 'bg-accent border-accent group-hover:bg-accent/80'
: 'bg-fg/50 border-fg/50 group-hover:bg-fg/70'
"
aria-hidden="true"
>
<span
class="block h-5 w-5 rounded-full bg-bg shadow-sm transition-transform duration-200 ease-in-out motion-reduce:transition-none"
/>
</span>
</button>
<label :for="id">
<span class="toggle--label-text">{{ label }}</span>
<input
role="switch"
type="checkbox"
:id
class="toggle--checkbox"
:class="dir"
v-model="checked"
/>
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
<span class="toggle--background"></span>
</label>
<p v-if="description" class="text-sm text-fg-muted mt-2">
{{ description }}
</p>
</template>

<style scoped>
button[aria-checked='false'] > span:last-of-type > span {
translate: 0;
.toggle--label-text {
grid-area: label-text;
}

.toggle--background {
grid-area: toggle-background;
justify-self: end;
}

.toggle--checkbox {
opacity: 0;
}

label {
display: grid;
grid-template-areas: 'label-text . toggle-background';
}

input {
grid-row: 1;
grid-column: 3;
justify-self: end;
}

/* background */
.toggle--background {
width: 44px;
height: 24px;
background: var(--fg-subtle);
border-radius: 9999px;
border: 1px solid var(--fg);
display: flex;
position: relative;
}
Comment thread
abbeyperini marked this conversation as resolved.

label:has(input:focus) .toggle--background {
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
outline: solid 2px var(--fg);
outline-offset: 2px;
}

label:has(input:checked) .toggle--background {
background: var(--fg);
border-color: var(--fg);
}
button[aria-checked='true'] > span:last-of-type > span {
translate: calc(100%);

label:has(input:hover) .toggle--background {
background: var(--fg-muted);
}
html[dir='rtl'] button[aria-checked='true'] > span:last-of-type > span {
translate: calc(-100%);

/* Circle that moves */
.toggle--checkbox:checked + .toggle--background:before {
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
animation-fill-mode: forwards;
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
transition: transform 200ms ease-in-out;
}

.toggle--background:before {
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
animation-fill-mode: forwards;
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
transition: transform 200ms ease-in-out;
content: '';
width: 20px;
height: 20px;
top: 1px;
position: absolute;
border-radius: 9999px;
background: var(--bg);
}

/* Support rtl locales */
.toggle--checkbox.ltr + .toggle--background:before {
left: 3px;
}

.toggle--checkbox.rtl + .toggle--background:before {
right: 3px;
}
Comment thread
abbeyperini marked this conversation as resolved.
Outdated

.toggle--checkbox:checked.ltr + .toggle--background:before {
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
transform: translate(17px);
}

@media (forced-colors: active) {
/* make toggle tracks and thumb visible in forced colors. */
button[role='switch'] {
& > span:last-of-type {
forced-color-adjust: none;
}

&[aria-checked='false'] > span:last-of-type {
background: Canvas;
border-color: CanvasText;

& > span {
background: CanvasText;
}
}

&[aria-checked='true'] > span:last-of-type {
background: Highlight;
border-color: Highlight;

& > span {
background: HighlightText;
}
}
}
.toggle--checkbox:checked.rtl + .toggle--background:before {
Comment thread
abbeyperini marked this conversation as resolved.
Outdated
transform: translate(-17px);
}
Comment thread
knowler marked this conversation as resolved.
</style>
5 changes: 3 additions & 2 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ describe('component accessibility audits', () => {
describe('Toggle', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(SettingsToggle, {
props: { label: 'Enable feature' },
props: { label: 'Enable feature', modelValue: false },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
Expand All @@ -2401,6 +2401,7 @@ describe('component accessibility audits', () => {
props: {
label: 'Enable feature',
description: 'This enables the feature',
modelValue: false,
},
})
const results = await runAxe(component)
Expand Down Expand Up @@ -2538,7 +2539,7 @@ describe('background theme accessibility', () => {
name: 'SettingsToggle',
mount: () =>
mountSuspended(SettingsToggle, {
props: { label: 'Feature', description: 'Desc' },
props: { label: 'Feature', description: 'Desc', modelValue: false },
}),
},
{
Expand Down
Loading