# Label

Renders an accessible caption for a form control.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { VStack } from "@/components/ui/stack"

const styles = stylex.create({
  field: { width: "100%", maxWidth: "20rem" },
})

export default function LabelDemo() {
  return (
    <VStack gap={2} xstyle={styles.field}>
      <Label htmlFor="label-demo-email">Email</Label>
      <Input id="label-demo-email" type="email" placeholder="me@example.com" />
    </VStack>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation), and paste `states.ts` from the same page,
   which is where the dimmed disabled label comes from. The label also needs
   `interop.css`, which sizes an icon the label does not render.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/label.tsx"
   "use client"

   import { useRender } from "@base-ui/react/use-render"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"

   import { states } from "@/theme/states"
   import { space, text } from "@/theme/tokens.stylex"

   const styles = stylex.create({
     base: {
       display: "flex",
       alignItems: "center",
       gap: space.s2,
       userSelect: "none",
       fontSize: text.sm,
       lineHeight: 1,
       fontWeight: 500,
       "--carbon-icon-size": space.s4,
     },
   })

   const labelStyles = { base: styles.base, disabled: states.disabled }

   type LabelProps = useRender.ComponentProps<"label", { disabled: boolean | undefined }> & {
     disabled?: boolean
     xstyle?: StyleXStyles
     className?: string
   }

   function Label({ render, ref, style, disabled, xstyle, className, ...props }: LabelProps) {
     const sx = stylex.props(styles.base, disabled && states.disabled, xstyle)

     return (
       useRender({
         defaultTagName: "label",
         render,
         ref,
         state: { disabled },
         props: {
           "data-slot": "label",
           ...sx,
           ...props,
           className: clsx("carbon-icons", sx.className, className),
           style: { ...sx.style, ...style },
         },
       })
     )
   }

   export { Label, labelStyles }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import { Label } from "@/components/ui/label"
```

```tsx
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />
```

## Disabled

Use the `disabled` prop to dim the label and take it out of the hit test. It is
a prop rather than an inherited state. Inside a [Field](https://carbon.style/docs/components/field)
the `FieldLabel` reads it from the field; a bare label has nothing to read it
from.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { VStack } from "@/components/ui/stack"

const styles = stylex.create({
  field: { width: "100%", maxWidth: "20rem" },
})

export default function LabelDisabled() {
  return (
    <VStack gap={2} xstyle={styles.field}>
      <Label htmlFor="label-disabled-plan" disabled>
        Billing plan
      </Label>
      <Input id="label-disabled-plan" defaultValue="Enterprise" disabled />
    </VStack>
  )
}
```

## Content

The label is a flex row with a `0.5rem` gap, so an icon or a
[Badge](https://carbon.style/docs/components/badge) sits beside the text without extra markup.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { VStack } from "@/components/ui/stack"
import { Lock } from "lucide-react"

const styles = stylex.create({
  field: { width: "100%", maxWidth: "20rem" },
})

export default function LabelContent() {
  return (
    <VStack gap={2} xstyle={styles.field}>
      <Label htmlFor="label-content-secret">
        <Lock />
        Signing secret
        <Badge variant="secondary">Recommended</Badge>
      </Label>
      <Input id="label-content-secret" type="password" placeholder="whsec_..." />
    </VStack>
  )
}
```

> **Note**
>
> Give the label either an `htmlFor` matching the control's `id` or the control
> itself as a child. Without one of the two the label is decoration, and clicking
> it does not focus anything.

## API reference

Pass `render` to replace a part's element or compose it with another component.

`Label` renders a `<label>` and accepts every `<label>` attribute.

### Label

| Prop        | Type           | Default |
| ----------- | -------------- | ------- |
| `disabled`  | `boolean`      | `false` |
| `htmlFor`   | `string`       | —       |
| `xstyle`    | `StyleXStyles` | —       |
| `className` | `string`       | —       |

The line height is `1` rather than the `sm` leading the type scale pairs with
`text.sm`. A label is one line. With the tight box, the stack's gap alone sets the
distance to the control below.

### labelStyles

The style maps behind the component. Spread them onto any element.

| Key        | What it is                            |
| ---------- | ------------------------------------- |
| `base`     | The row: gap, type, weight, icon size |
| `disabled` | The dimmed, non-interactive state     |

`disabled` is `states.disabled` from `theme/states.ts`, the same dimmed style
every control uses.

### Data attributes

| Attribute           | On        | Effect                          |
| ------------------- | --------- | ------------------------------- |
| `data-slot="label"` | the label | Targets the label from a parent |
| `data-disabled`     | the label | Present when `disabled` is set  |
