Fieldset
Groups related controls under one legend, and turns all of them off at once.
Fieldset puts a heading on a group of controls and lets you disable the whole group in one go. Reach for it when several fields belong together, like an address or a set of payment details, and the person filling the form should see that they do.
<template>
<div class="flex w-[19rem] flex-col gap-8">
<Fieldset.Root :class="fieldset">
<Fieldset.Legend :class="legend">Shipping address</Fieldset.Legend>
<div class="flex flex-col gap-3">
<Field.Root
class="group flex flex-col gap-1.5"
:options="{ name: 'recipient' }"
>
<div :class="box">
<div :class="stack">
<span :class="line">
<Input
v-model="recipient"
:class="control"
placeholder=" "
autocomplete="name"
/>
</span>
<Field.Label :class="label">Recipient</Field.Label>
</div>
</div>
</Field.Root>
<Field.Root
class="group flex flex-col gap-1.5"
:options="{ name: 'street' }"
>
<div :class="box">
<div :class="stack">
<span :class="line">
<Input
v-model="street"
:class="control"
placeholder=" "
autocomplete="street-address"
/>
</span>
<Field.Label :class="label">Street and number</Field.Label>
</div>
</div>
</Field.Root>
</div>
</Fieldset.Root>
<Fieldset.Root :class="fieldset" :options="{ disabled: true }">
<Fieldset.Legend :class="legend">Billing address</Fieldset.Legend>
<div class="flex flex-col gap-3">
<Field.Root
class="group flex flex-col gap-1.5"
:options="{ name: 'billing' }"
>
<div :class="box">
<div :class="stack">
<span :class="line">
<Input :class="control" placeholder=" " />
</span>
<Field.Label :class="label">Same as shipping</Field.Label>
</div>
</div>
</Field.Root>
</div>
</Fieldset.Root>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Field } from '@maas/mirror/vue/Field'
import { Fieldset } from '@maas/mirror/vue/Fieldset'
import { Input } from '@maas/mirror/vue/Input'
const recipient = ref('')
const street = ref('')
const fieldset = 'group/fieldset border-0 p-0'
const legend =
'type-component-md text-surface mb-3 group-data-[disabled=true]/fieldset:text-disabled-muted'
const box = [
'relative isolate flex h-12 w-full cursor-text items-center justify-between gap-1.5',
'rounded-component-lg border-surface border-2 px-[0.875rem]',
'transition-all duration-100 ease-linear [&_*]:transition-all [&_*]:duration-100 [&_*]:ease-linear',
'outline-4 outline-transparent focus-within:focus-ring',
'group-[[data-invalid=true]:not([data-focused=true])]:border-danger-subtle',
'group-[[data-invalid=true]:not([data-focused=true])]:bg-danger-subtle',
'group-data-[disabled=true]/fieldset:border-disabled-subtle group-data-[disabled=true]/fieldset:cursor-not-allowed',
].join(' ')
const stack = [
'group/stack flex h-full max-h-full w-full flex-col-reverse',
'items-center justify-center gap-0 px-1',
'focus-within:gap-1 has-[input:not(:placeholder-shown)]:gap-1',
].join(' ')
const line = [
'relative flex h-0 w-full items-end',
'group-focus-within/stack:h-[0.9375rem]',
'has-[input:not(:placeholder-shown)]:h-[0.9375rem]',
].join(' ')
const control = [
'type-component-lg leading-[normal]! text-surface',
'block box-content h-[1lh] w-full py-1.25 -my-1.25 appearance-none bg-transparent outline-none',
'placeholder:text-transparent',
'group-data-[disabled=true]/fieldset:text-disabled-solid',
].join(' ')
const label = [
'type-component-lg leading-[normal]! text-surface-muted',
'flex h-full w-full items-center [--mirror-label-cursor:text]',
"before:absolute before:-inset-0.5 before:-z-10 before:content-['']",
'group-focus-within/stack:h-auto group-focus-within/stack:text-[0.6875rem] group-focus-within/stack:[--mirror-label-cursor:auto]',
'group-has-[input:not(:placeholder-shown)]/stack:h-auto group-has-[input:not(:placeholder-shown)]/stack:text-[0.6875rem] group-has-[input:not(:placeholder-shown)]/stack:[--mirror-label-cursor:auto]',
'group-data-[disabled=true]/fieldset:text-disabled-muted',
].join(' ')
</script>
The second group is disabled, so the browser will not let anyone type in the control inside it.
Usage guidelines
- Give every fieldset a legend. Without one a screen reader announces the group with no name at all.must
- Browsers give a fieldset
min-width: min-content, which stops it shrinking inside a flex or grid parent.Fieldset.Rootsets it back to0, and--mirror-fieldset-min-inline-sizeis there if you want the old behavior.may - A native
<legend>has to be the first child of the fieldset, and a fieldset that lays its children out with flex or grid renders it in ways that differ per browser. If you need the layout, turnnativeLegendoff and keep the legend in normal flow.should disabledreaches the controls through the browser, not through Mirror, so the controls inside do not writedata-disabledthemselves. Style them from the fieldset instead, the way the demo does.should