RadixUI styling Radio groups

3.2k Views Asked by At

I am fairly new to Radix UI (with Shadcn/ui) and styling components for that matter. Couldn't figure out a solution for adapting a JSX component to Radix UI:

I am using Radix UI Radio Groups & want to style my component which would look like this upon selection, basically it's this component from Hyper UI called grid with stacked content and checked: Required result

Some notes

  • I am using NextJS 13, typescript and well, React :D
  • With small adjustment to my code below I can make that radio icon would appear upon select, but not borders wont get active.
  • The idea is to have the block border to have these borders (not selected ones will have a thin border.
  • Currently in the code I added {children} to RadioGroupPrimitive.Indicator I know it's wrong as it only handles the button, so the text appears only on select.

I was thinking that I should play with labels somehow, but as the selection is the whole block + icon it seems that I cant put the title and subtitle correctly inside.

I currently am trying to play around and what I came around to is:

const RadioGroupItemModern = React.forwardRef<
  React.ElementRef<typeof RadioGroupPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, children, ...props }, ref) => {
  return (
    <RadioGroupPrimitive.Item
      ref={ref}
      className={cn(
        `block w-full bg-white p-3 hover:border-gray-200 focus:outline-none checked:ring-primary `,
        // "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
        className
      )}
      {...props}
    >

    
        <RadioGroupPrimitive.Indicator className="p-4 cursor-pointer rounded-lg border ring-2 ring-primary flex justify-between items-center hover:border-gray-200 focus:ring-2" >
        <div>{children}</div>
          <Icons.checkCircle3 className="h-6 w-6 text-current" />
        </RadioGroupPrimitive.Indicator>
    </RadioGroupPrimitive.Item>


  )
})
RadioGroupItemModern.displayName = RadioGroupPrimitive.Item.displayName

And my front-end call would look like this:

<RadioGroupItemModern key={choice.value} value={choice.value} id={choice.value} defaultValue={field.value || ''} >
    <div>
    <RadioGroupItemTitle>{choice.text}</RadioGroupItemTitle>
    <RadioGroupItemSubtitle>{choice.description}</RadioGroupItemSubtitle>
    </div>
</RadioGroupItemModern>

So now it only shows me the required result when I press the selection (which in this case is invisible).

I tried playing around with the Radix Primitives, adding children in different ways but as I can't figure out the logic on how this would work correctly can't figure out a way as Radix has group radio and labels next to them as different components.

3

There are 3 best solutions below

1
On

Forgot to add my answer here when I found an implementation. Right inside the Examples from the library creator shadcn, basically he's leveraging the presence of &:has([data-state=checked] an attribute that Radix UI itself uses to handle the checked item.

He uses the attribute as a boolean to apply the border-primary class:

<FormLabel className="[&:has([data-state=checked])>div]:border-primary">

For more info have a look at the GitHub Repo right here.

0
On

You have to use peer from TailwindCSS & aria-checked: instead of checked: because the Radix component wraps the input in a button. See the smallest possible implementation with RadixUI & shadcn/ui here:

<FormItem className="block w-full space-x-3 space-y-0">
  <FormControl>
    <RadioGroupItem value="myValue" className="peer hidden" />
  </FormControl>
  <FormLabel className="block cursor-pointer rounded-md border border-border p-4 font-normal shadow-sm hover:border-primary peer-aria-checked:border-primary peer-aria-checked:ring-1 peer-aria-checked:ring-ring">
    My Label
  </FormLabel>
</FormItem>
0
On

I found that the best answer for this is actually in the shadcn/ui examples here.

In this example, there are two important sets of classes you need to apply:

  1. The first is to each RadioGroupItem inside your RadioGroup. They should have the classes peer sr-only. The important one here is peer, as stated in another answer, that comes form Tailwind.

  2. The second is to the element that you want to change the visual appearance of when that RadioGroupItem is picked. From the example above, a border is applied to the Label inside the RadioGroupItem using an attribute selector: peer-data-[state=checked]:border-primary. It looks at the data-state attribute's value on the button element the RadioGroupItem generates to determine when to apply the Tailwind class; in this case it's applying a border-primary, but that can be anything, including hiding/showing an SVG or whatever.

This should apply for RadixUI and shadcn/ui components, but the method is just using standard attribute selectors.

Also, if you're wondering what peer is, here is the relevant documentation from Tailwind!