From c8ccd2e96b5ce4d667a4e86116c6598e16d0c4bd Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Thu, 2 Jul 2026 13:31:19 -0400 Subject: [PATCH] fix(ui): keep checked checkbox checkmark legible across themes --- .changeset/checkbox-dark-mode-checkmark.md | 5 ++++ packages/ui/src/primitives/Input.tsx | 30 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 .changeset/checkbox-dark-mode-checkmark.md diff --git a/.changeset/checkbox-dark-mode-checkmark.md b/.changeset/checkbox-dark-mode-checkmark.md new file mode 100644 index 00000000000..318e5d25dc2 --- /dev/null +++ b/.changeset/checkbox-dark-mode-checkmark.md @@ -0,0 +1,5 @@ +--- +"@clerk/ui": patch +--- + +Fix the checked checkbox appearing as a blank filled box in dark themes. The checkmark now uses the `colorPrimaryForeground` theme color, so it stays legible against the checkbox background across light, dark, and custom themes. diff --git a/packages/ui/src/primitives/Input.tsx b/packages/ui/src/primitives/Input.tsx index 6ef03a6e9f2..1f01aaefe71 100644 --- a/packages/ui/src/primitives/Input.tsx +++ b/packages/ui/src/primitives/Input.tsx @@ -6,6 +6,15 @@ import { common, createVariants, mqu } from '../styledSystem'; import { sanitizeInputProps, useFormField } from './hooks/useFormField'; import { useInput } from './hooks/useInput'; +/** + * Checkmark shape for a checked checkbox, used as a CSS mask rather than a tinted + * background-image. Only the shape's alpha matters here; the visible checkmark color + * comes from `backgroundColor` on the masked element, which lets it reference the + * `colorPrimaryForeground` theme token (a CSS variable / `light-dark()` value would + * not resolve if it were baked into the SVG fill). + */ +const checkboxCheckmarkMask = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23000' fill-rule='evenodd' d='M7.712.233a.889.889 0 0 1 .055 1.256C6.742 2.61 6.249 3.291 5.508 4.615c-.279.5-.589 1.194-.835 1.784a36.761 36.761 0 0 0-.382.95l-.021.057-.006.014-.001.003a.89.89 0 0 1-1.504.27L.218 4.765A.889.889 0 1 1 1.56 3.6l1.591 1.834c.235-.548.524-1.181.806-1.685.807-1.445 1.38-2.239 2.499-3.46A.889.889 0 0 1 7.712.234Z' clip-rule='evenodd'/%3E%3C/svg%3E")`; + const { applyVariants, filterProps } = createVariants((theme, props) => ({ base: { boxSizing: 'border-box', @@ -24,12 +33,25 @@ const { applyVariants, filterProps } = createVariants((theme, props) => ({ appearance: 'none', height: theme.sizes.$4, padding: theme.space.$1, - backgroundSize: `${theme.sizes.$2} ${theme.sizes.$2}`, - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', '&:checked': { - backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 8 8'%3E%3Cpath fill='${theme.colors.$white}' fill-rule='evenodd' d='M7.712.233a.889.889 0 0 1 .055 1.256C6.742 2.61 6.249 3.291 5.508 4.615c-.279.5-.589 1.194-.835 1.784a36.761 36.761 0 0 0-.382.95l-.021.057-.006.014-.001.003a.89.89 0 0 1-1.504.27L.218 4.765A.889.889 0 1 1 1.56 3.6l1.591 1.834c.235-.548.524-1.181.806-1.685.807-1.445 1.38-2.239 2.499-3.46A.889.889 0 0 1 7.712.234Z' clip-rule='evenodd'/%3E%3C/svg%3E")`, + position: 'relative', backgroundColor: theme.colors.$primary500, + // Draw the checkmark on a masked overlay so its color tracks the + // colorPrimaryForeground token and stays legible in every theme. + '&::before': { + content: '""', + position: 'absolute', + inset: 0, + backgroundColor: theme.colors.$colorPrimaryForeground, + maskImage: checkboxCheckmarkMask, + WebkitMaskImage: checkboxCheckmarkMask, + maskPosition: 'center', + WebkitMaskPosition: 'center', + maskRepeat: 'no-repeat', + WebkitMaskRepeat: 'no-repeat', + maskSize: `${theme.sizes.$2} ${theme.sizes.$2}`, + WebkitMaskSize: `${theme.sizes.$2} ${theme.sizes.$2}`, + }, }, } : {}),