Native Radio Group

Styled native HTML radio buttons that look like the Radio Group and work without JavaScript.

Basic
With FieldSet
Battery Level

Choose your preferred battery level.

Disabled
Invalid
Notification Preferences

Choose how you want to receive notifications.

<script lang="ts">
  import { Example, ExampleWrapper } from '#lib/components/example';
  import { Field, FieldDescription, FieldLabel, FieldLegend, FieldSet } from '#lib/components/ui/field';

Installation

pnpm dlx @sveltery/ui@latest add native-radio-group

Usage

<script lang="ts">
  import { NativeRadio, NativeRadioGroup } from '#lib/components/ui/native-radio-group';
  import { Label } from '#lib/components/ui/label';
</script>
<NativeRadioGroup aria-label="Density">
  <div class="flex items-center gap-2">
    <NativeRadio name="density" value="default" id="r1" />
    <Label for="r1">Default</Label>
  </div>
  <div class="flex items-center gap-2">
    <NativeRadio name="density" value="comfortable" id="r2" defaultChecked />
    <Label for="r2">Comfortable</Label>
  </div>
</NativeRadioGroup>

NativeRadio is a real <input type="radio">, and every prop goes on it. Give the radios of one group the same name. Mark the initial choice with defaultChecked. A form reset returns to it, as it does for a native radio. checked sets the current choice, which is what a remote form field passes.

When to use it

Use NativeRadioGroup when the form must work before the page hydrates or with JavaScript off. The browser does all of the work:

  • A click or tap on the radio or its label picks it.
  • The arrow keys move the choice, and the group is one tab stop.
  • The checked radio submits with its form.

Radio Group runs on the Sveltery Base RadioGroup, which renders a <span> and a hidden input. Without JavaScript, only a click on its label picks a radio there, and the keyboard does nothing. Radio Group also gives you what NativeRadioGroup does not have:

  • Base UI state attributes, such as data-checked and data-disabled, to style on. Style NativeRadio with checked:, disabled: and aria-invalid: instead.
  • bind:value and onValueChange for the group's value.
  • Base's Field behavior: validation, data-dirty and data-touched, and Form errors.
  • Radio.Indicator and its animations. The dot of NativeRadio is a background.

The Field layout parts, such as Field, FieldLabel, FieldSet and FieldError, work with both.

Remote forms

Spread a remote form field onto each radio with as('radio', value). The field gives each radio its name, value, checked state and aria-invalid.

Plan

Enterprise shows an issue from the server.

<script lang="ts">
  import { Button } from '#lib/components/ui/button';
  import { Field, FieldDescription, FieldError, FieldLabel, FieldLegend, FieldSet } from '#lib/components/ui/field';
<script lang="ts">
  import { Button } from '#lib/components/ui/button';
  import { Field, FieldError, FieldLabel, FieldLegend, FieldSet } from '#lib/components/ui/field';
  import { NativeRadio, NativeRadioGroup } from '#lib/components/ui/native-radio-group';
  import { planSchema, plans } from '#lib/forms/plan';
  import { choosePlan } from '#lib/forms/plan.remote';

  const f = choosePlan.preflight(planSchema);
</script>

<form {...f}>
  <FieldSet>
    <FieldLegend>Plan</FieldLegend>
    <NativeRadioGroup>
      {#each plans as plan (plan)}
        <Field orientation="horizontal" data-invalid={!!f.fields.plan.issues()}>
          <NativeRadio id="plan-{plan}" {...f.fields.plan.as('radio', plan)} />
          <FieldLabel for="plan-{plan}" class="font-normal">{plan}</FieldLabel>
        </Field>
      {/each}
    </NativeRadioGroup>
    <FieldError errors={f.fields.plan.issues()} />
  </FieldSet>
  <Button type="submit" disabled={f.pending > 0}>Choose plan</Button>
</form>

Without JavaScript, the form posts to the server, and the page comes back with the submitted radio checked and the issue shown.