Skip to content

[Bug]: Checkbox renders invalid markup if used inside a form #2384

@wireless25

Description

@wireless25

Environment

not relevant

Link to minimal reproduction

Steps to reproduce

Use the Checkbox inside a form and add a name attribute to identify the field in the form data later:

<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { CheckboxIndicator, CheckboxRoot, Label } from 'reka-ui'
import { ref } from 'vue'

const checkboxTerms = ref(true)
</script>

<template>
<form @submit.prevent="console.log($event)">
  <div class="flex gap-2.5">
    <CheckboxRoot
      id="terms-checkbox"
      name="terms"
      v-model="checkboxTerms"
      class="flex h-5 w-5 items-center justify-center border"
    >
      <CheckboxIndicator class="h-full w-full flex items-center justify-center">
        <Check
          icon="radix-icons:check"
          class="h-4 w-4"
        />
      </CheckboxIndicator>
    </CheckboxRoot>
    <Label for="terms-checkbox" class="flex flex-row gap-4 items-center">
      <span class="text-sm">Accept terms and conditions.</span>
    </Label>
  </div>
  <button type="submit" class="mt-4 px-4 py-2 bg-blue-600 text-white hover:bg-blue-700">Submit</button>
</form>
</template>

Describe the bug

When used inside a form, the Checkbox component renders a visually hidden input to add the value to submission and bubble events, which is also stated in the docs. This leads to two problems:

  1. Invalid markup: According to the MDN docs, an input element is only allowed inside a button when the type is set to hidden. MDN: button permitted content and MDN: interactive content. The Checkbox component renders a button as root by default, also the input uses the type checkbox, so this leads to invalid HTML markup.
  2. A11y issue: The visually hidden input is not actually hidden by type, but only rendered offscreen. This leads to an a11y error according to Axe, as the input needs a label (its not hidden).

You can at least omit the second issue with wrapping the checkbox in a <label> as it is done in the Reka-UI docs, but for example in the shadcn-vue docs a Label component is used as sibling, which leads to the mentioned issues.

Expected behavior

The Checkbox component renders valid markup by default, also with a label as sibling. Render the visually hidden input as a sibling of the button and pass a label to the input.

Context & Screenshots (if applicable)

Image

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions