spartan icon indicating copy to clipboard operation
spartan copied to clipboard

Radio component tightly couples the label, limiting flexibility

Open OmerGronich opened this issue 7 months ago • 2 comments

Which scope/s are relevant/related to the feature request?

radio-group


Information

Currently, the Radio component "swallows" the <label> element, meaning the label is rendered internally by the component itself:

<label [for]="inputId()" data-slot="label">
  <ng-content />
</label>

While this pattern is common in libraries like Angular Material, it introduces limitations when building a design system and composing custom UIs. Specifically:

  • It inconsistently diverges from other components in Spartan like Checkbox and Switch, which do not include an internal label and allow external labeling.
  • It prevents flexible layout composition, especially when integrating radio buttons into higher-level UI components like layout blocks, wrappers, or form field layouts.

Here’s a look at the current internal structure of the Radio component:

<input
  #input
  type="radio"
  [id]="inputId()"
  [checked]="checked()"
  [disabled]="disabledState()"
  [tabIndex]="tabIndex()"
  [attr.name]="radioGroup.name()"
  [attr.value]="value()"
  [required]="required()"
  (change)="onInputInteraction($event)"
  (click)="onInputClick($event)"
/>
<label [for]="inputId()" data-slot="label">
  <ng-content />
</label>

Why this matters

Libraries like Taiga UI and shadcn which Spartan NG proudly builds on top of—treat the radio and label as separate elements. This is much more composable and developer-friendly:

Shadcn:

<RadioGroup defaultValue="comfortable">
  <div className="flex items-center space-x-2">
    <RadioGroupItem value="default" id="r1" />
    <Label htmlFor="r1">Default</Label>
  </div>
  <div className="flex items-center space-x-2">
    <RadioGroupItem value="comfortable" id="r2" />
    <Label htmlFor="r2">Comfortable</Label>
  </div>
</RadioGroup>

Taiga:

<label tuiBlock="s">
        <input
            formControlName="testValue5"
            size="s"
            tuiRadio
            type="radio"
            value="qiwi"
        />
        <span tuiTitle>
            <span>
                Qiwi
                <tui-icon tuiTooltip="Not the bird" />
            </span>
            <span tuiSubtitle>Green and sour</span>
        </span>
</label>

This separation allows for:

  • Custom alignment and positioning of radio buttons and labels
  • Uniform composition across different selection controls
  • Easier accessibility testing and configuration

Suggested Change

Remove the internal <label> and expose only the hidden native input. Developers should then be responsible for providing an external label using <label for="...">, just like Checkbox and Switch.

This would bring consistency, flexibility, and better alignment with Spartan's compositional philosophy.


Describe any alternatives/workarounds you're currently using

The current workaround is to style the projected content carefully and override internal structure via CSS. But this is brittle and limits the ability to integrate Radio into larger, reusable UI layouts.


I would be willing to submit a PR to fix this issue

  • [x] Yes
  • [ ] No

OmerGronich avatar Apr 04 '25 23:04 OmerGronich

We think this is a reasonable change and would like to make that change before we release v1! You indicated that you are willing to submit a PR. Let me know if there is anything I can do to support you!

goetzrobin avatar Apr 08 '25 12:04 goetzrobin

This might also simplify how we apply different layouts like card styles (#246)

marcjulian avatar Apr 09 '25 14:04 marcjulian