# Label

Names a control, and wires the association up for you.

`Label` renders the `<label>` for a control and points it at the right element.
Inside a [`Field`](/components/field) it needs no props at all; outside one,
give it a `for`.

::component-preview{name="LabelPreview"}
```vue
<template>
  <div class="flex w-[17rem] flex-col gap-6">
    <Field.Root class="group flex flex-col gap-1.5">
      <div :class="box">
        <div :class="stack">
          <span :class="line">
            <Input v-model="workspace" :class="control" placeholder=" " />
          </span>
          <Label :class="label">Workspace</Label>
        </div>
      </div>
    </Field.Root>

    <Field.Root
      class="group flex flex-col gap-1.5"
      :options="{ disabled: true }"
    >
      <div :class="box">
        <div :class="stack">
          <span :class="line">
            <Input
              :class="control"
              model-value="eu-central-1"
              placeholder=" "
            />
          </span>
          <Label :class="label">Region</Label>
        </div>
      </div>
    </Field.Root>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Field, Input, Label } from '@maas/mirror/vue'

const workspace = ref('')

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]:border-disabled-subtle group-data-[disabled=true]: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-invalid=true]:not([data-focused=true])]:text-danger-on-muted',
  'data-[disabled=true]: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-invalid=true]:not([data-focused=true])]:text-danger-muted',
  'group-data-[disabled=true]:text-disabled-muted',
].join(' ')
</script>
```
::

## Usage guidelines

- Give a second label in the same field its own `id`, or both take the field’s
  label ID and register it twice.
- A non-native label needs something to point at, so without a `for` and without
  a `Field` to inject one it throws `missing_label_target`.
- Inside a `Field`, a non-native label names the control from the other side:
  the control adds `aria-labelledby` for it, whether or not it could have been a
  `<label for>` target.

## Anatomy

Render it beside the control it names.

::component-anatomy
---
parts:
  - name: Label
    required: true
    description: Renders a <label>, or a <span> when nativeLabel is false.
---
::

```vue
<script setup lang="ts">
import { Field, Input, Label } from '@maas/mirror/vue'
</script>

<template>
  <Field.Root>
    <Label>Email</Label>
    <Input v-model="email" />
  </Field.Root>
</template>
```

## Examples

### Inside a field

`for` points at the field’s control, and the label registers its own ID back, so
a control that cannot be a `<label for>` target names itself through
`aria-labelledby` instead.

```vue
<template>
  <Field.Root :options="{ name: 'email' }">
    <Label>Email</Label>
    <Input v-model="email" />
  </Field.Root>
</template>
```

The label writes the field state set onto its element, so it paints itself from
the control’s validity without a bound class.

```vue
<template>
  <Field.Root :options="{ name: 'email', validate: validateEmail }">
    <Label>Email</Label>
    <Input v-model="email" />
  </Field.Root>
</template>

<style>
.mirror-label[data-invalid='true'] {
  color: var(--app-color-danger-fg-muted);
}

.mirror-label[data-required='true']::after {
  content: '*';
}
</style>
```

### Outside a field

Given a `for`, it behaves as a plain `<label>` with `data-scope` and nothing
else, since there is no field state for it to report.

```vue
<template>
  <Label for="search">Search</Label>
  <input id="search" type="search" />
</template>
```

### Non-native labels

Some controls cannot be the target of a native `<label for>`. Set `nativeLabel`
to `false` for those and `Label` renders a `<span>` with an ID instead.

```vue
<template>
  <Label id="volume-label" for="volume" :native-label="false">Volume</Label>
  <div id="volume" role="slider" tabindex="0" aria-labelledby="volume-label" />
</template>
```

Inside a `Field` the wiring is done for you. The label drops its `for` and the
field’s control picks up `aria-labelledby` in its place, so the association
holds even over an `Input` that a native label could have named.

```vue
<template>
  <Field.Root :options="{ name: 'volume' }">
    <Label :native-label="false">Volume</Label>
    <Input v-model="volume" />
  </Field.Root>
</template>
```

## API reference

**Standalone.** Ordinary props, no store. `Field.Label` is this component,
re-exported under the `Field` namespace.

### `Label`

Renders a `<label>`, or a `<span>` when `nativeLabel` is `false`.

#### Props

::docs-table
---
columns:
  - label: Prop
  - label: Type
  - label: Default
rows:
  - items:
      - label: id
        description: The element’s DOM ID. Registered with the surrounding `Field`.
      - label: string
      - label: the field’s `labelId`, else generated
        plaintext: true
  - items:
      - label: for
        description: The ID of the control this labels. Applied only when `nativeLabel` is `true`.
      - label: string
      - label: injected `controlId`
        plaintext: true
  - items:
      - label: nativeLabel
        description: 'Renders a `<label>`, or a [`<span>`](#non-native-labels) when `false`. `as` follows, defaulting to `label` or `span`.'
      - label: boolean
      - label: 'true'
---
::

#### Slot props

The [field state set](/components/styling#the-field-state-set).

#### Data attributes

The field state set, plus `data-scope="label"`.

::data-attributes
::

#### CSS variables

::css-variables
::

#### Errors

::docs-table
---
columns:
  - label: Code
rows:
  - items:
      - label: missing_label_target
        description: '`nativeLabel` is `false`, no `for` was given, and no `Field` context supplied a `controlId`.'
---
::

## Accessibility

No roles and no keyboard behaviour of its own. A click forwards focus to the
control, and a non-native label cancels the default on `pointerdown` and calls
`focus()` on the target, so a custom control behaves the same way. Inside a
`Field`, a non-native label is listed in the control’s `aria-labelledby`, so
the naming holds either way.

`data-disabled` comes from the field, so a `Label` given a `for` outside a field
never reports one and you should style the control instead.
