radix-ui / primitives

Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
https://radix-ui.com/primitives
MIT License
15.95k stars 834 forks source link

Radio Input Attribute checked can be faulty if set to boolean #3158

Open maxkarkowski opened 2 months ago

maxkarkowski commented 2 months ago

Hi, i ran into issue when styling radio group items with the new :has css styling. i have a css like this

// sets interactive color if element is checked
// aria checked attribute is necessary if component is used without a form
      &:has(*[checked="true"]):not(:has(*:disabled)),
      &:has(*[data-state="checked"]):not(:has(*:disabled)) {
        background-color: red;
      }

if i switch between two radio button and then switch routes and go back via back button, both radio input are getting the style even though only one input radio has the attribute checked in the web inspector.

But if i select the items via the browser console, the "unchecked" radio item still hast checked true on the element.

After a little bit of digging it seems that setting the checked attribute is not the valid way.

https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes

The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.

If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.

Here is the code in your primitive. https://github.com/radix-ui/primitives/blob/6469d41dcc6e84fe41d70a2703924338e7562dd1/packages/react/radio-group/src/Radio.tsx#L80 I guess it would be better to 1) not setting the checked attribute entirely or 2) change the way of setting the attribute on the input radio

 {isFormControl && (
          <BubbleInput
            control={button}
            bubbles={!hasConsumerStoppedPropagationRef.current}
            name={name}
            value={value}
            // only set attribute 'checked' if true to checked="checked"
           // if false don't render attribute completely
            checked={checked ? "checked" : undefined}
            required={required}
            disabled={disabled}
            // We transform because the input is absolutely positioned but we have
            // rendered it **after** the button. This pulls it back to sit on top
            // of the button.
            style={{ transform: 'translateX(-100%)' }}
          />
        )}

This is maybe also the case for checkboxes https://github.com/radix-ui/primitives/blob/6469d41dcc6e84fe41d70a2703924338e7562dd1/packages/react/checkbox/src/Checkbox.tsx#L108