fix(desktop): stop the checkbox painting the check and dash at once

codicon.css styles glyphs through `.codicon[class*='codicon-']`, a
two-class selector that outranks Tailwind's single-class `hidden`. The
indicator stacks a check and a dash and hides one per state, so neither
was ever hidden and the box rendered both glyphs side by side, spilling
past its 16px bounds in every state including unchecked.

Use the important modifier on both display utilities so state, not
stylesheet order, decides which glyph shows.
This commit is contained in:
Brooklyn Nicholson 2026-07-30 00:17:26 -05:00
parent 71a86101ab
commit f790d5a6ce
2 changed files with 66 additions and 2 deletions

View file

@ -0,0 +1,62 @@
import { cleanup, render } from '@testing-library/react'
import { afterEach, beforeAll, describe, expect, it } from 'vitest'
import { Checkbox } from './checkbox'
/**
* The indicator stacks both glyphs and hides one with a utility class, so the
* cascade not the markup decides what the user sees. codicon.css sets
* `display: inline-block` on `.codicon[class*='codicon-']`, which outranks a
* single-class utility and paints the check and the dash at the same time.
* That specificity race is the real contract, so the test carries both
* stylesheets rather than asserting on class strings. Tailwind's nested output
* is flattened to the equivalent descendant selector because jsdom does not
* implement CSS nesting.
*/
const CODICON_CSS = `.codicon[class*='codicon-'] { display: inline-block; }`
const TAILWIND_CSS = `
.hidden\\! { display: none !important; }
:where(.group)[data-state="checked"] .group-data-\\[state\\=checked\\]\\:block\\! {
display: block !important;
}
:where(.group)[data-state="indeterminate"] .group-data-\\[state\\=indeterminate\\]\\:block\\! {
display: block !important;
}
`
function shownGlyphs(container: HTMLElement) {
return [...container.querySelectorAll<HTMLElement>('.codicon')]
.filter(glyph => getComputedStyle(glyph).display !== 'none')
.map(glyph => (glyph.classList.contains('codicon-check') ? 'check' : 'dash'))
}
beforeAll(() => {
// eslint-disable-next-line no-restricted-globals -- the cascade is the assertion; it needs a real stylesheet
const style = document.createElement('style')
style.textContent = `${CODICON_CSS}\n${TAILWIND_CSS}`
// eslint-disable-next-line no-restricted-globals -- see above
document.head.append(style)
})
afterEach(cleanup)
describe('Checkbox', () => {
it('paints the check alone when checked', () => {
const { container } = render(<Checkbox checked />)
expect(shownGlyphs(container)).toEqual(['check'])
})
it('paints the dash alone when indeterminate', () => {
const { container } = render(<Checkbox checked="indeterminate" />)
expect(shownGlyphs(container)).toEqual(['dash'])
})
it('paints no glyph when unchecked', () => {
const { container } = render(<Checkbox checked={false} />)
expect(shownGlyphs(container)).toEqual([])
})
})

View file

@ -18,8 +18,10 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP
className="flex items-center justify-center text-current"
data-slot="checkbox-indicator"
>
<Codicon className="hidden group-data-[state=checked]:block" name="check" size="0.875rem" />
<Codicon className="hidden group-data-[state=indeterminate]:block" name="dash" size="0.875rem" />
{/* codicon.css sets `display: inline-block` at higher specificity than a bare
`hidden`, so both glyphs paint at once without the important modifier. */}
<Codicon className="hidden! group-data-[state=checked]:block!" name="check" size="0.875rem" />
<Codicon className="hidden! group-data-[state=indeterminate]:block!" name="dash" size="0.875rem" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)