From da4fd17934d21991ea9c4604ad501f7981a84dd4 Mon Sep 17 00:00:00 2001 From: Matthew L McPeak Date: Tue, 14 Jul 2026 10:42:53 -0400 Subject: [PATCH] feat: smoother hover/select feedback on Checkbox, RadioGroup, Select - RadioGroupItem and SelectItem had no transition class at all, so their checked/highlighted background changes snapped instantly. Added transition-colors to both. - Added hover:border-ring to Checkbox and RadioGroupItem so unchecked items get visible mouse-hover feedback, not just focus-visible. - Wrapped the Checkbox checkmark and RadioGroup dot in a small Transition (fade-in/zoom-in on check, fade-out/zoom-out on uncheck) instead of popping in/out instantly, using the animation-duration-* utility (not duration-*) to avoid the transition:all bug fixed earlier in this session. Verified via headless Chromium: no console errors, hover border colors resolve to the ring token, Select items show a real transition-colors (not "all") with the expected 150ms duration. --- .../src/components/ui/checkbox/Checkbox.vue | 23 +++++++++++-------- .../ui/radio-group/RadioGroupItem.vue | 17 +++++++++----- .../src/components/ui/select/SelectItem.vue | 2 +- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/library/src/components/ui/checkbox/Checkbox.vue b/packages/library/src/components/ui/checkbox/Checkbox.vue index 29d285d..6b963ed 100644 --- a/packages/library/src/components/ui/checkbox/Checkbox.vue +++ b/packages/library/src/components/ui/checkbox/Checkbox.vue @@ -34,17 +34,22 @@ function toggle() { :aria-required="required" :data-state="checked ? 'checked' : 'unchecked'" :disabled="disabled" - :class="cn('border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" + :class="cn('border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50 hover:border-ring', props.class)" @click="toggle" > - - - - - + + + + + + diff --git a/packages/library/src/components/ui/radio-group/RadioGroupItem.vue b/packages/library/src/components/ui/radio-group/RadioGroupItem.vue index cd2d180..74e7671 100644 --- a/packages/library/src/components/ui/radio-group/RadioGroupItem.vue +++ b/packages/library/src/components/ui/radio-group/RadioGroupItem.vue @@ -46,16 +46,21 @@ onBeforeUnmount(() => { :disabled="disabled || context.disabled.value" :class=" cn( - 'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 flex size-4 rounded-full focus-visible:ring-3 aria-invalid:ring-3 group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50', + 'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 flex size-4 rounded-full focus-visible:ring-3 aria-invalid:ring-3 group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50 transition-colors hover:border-ring', props.class, ) " @click="onClick" > - - - - - + + + + + + + diff --git a/packages/library/src/components/ui/select/SelectItem.vue b/packages/library/src/components/ui/select/SelectItem.vue index d2b7525..5a7b585 100644 --- a/packages/library/src/components/ui/select/SelectItem.vue +++ b/packages/library/src/components/ui/select/SelectItem.vue @@ -44,7 +44,7 @@ onMounted(() => { tabindex="-1" :class=" cn( - 'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0', + 'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 transition-colors', props.class, ) "