Label

ui-label is a styled caption for a form control. A native <label for> cannot reach a control inside another element's shadow root, so ui-label does the work itself. With for set to the id of a control, it copies its text to the control's aria-label (unless the control already has one), focuses the control when the label text is clicked, and toggles it when it is a checkbox, radio or switch.

It works with native controls and with ui-* controls such as ui-input, ui-checkbox and ui-switch.

Previewindex.html
Email
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Label demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-narrow {
      width: min(100%, calc(var(--spacing) * 88));
    }
    .demo-stack-tight {
      display: grid;
      gap: calc(var(--spacing) * 2);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>

  <div class="demo-narrow demo-stack-tight">
    <ui-label for="label-email">Email</ui-label>
    <ui-input id="label-email" type="email" placeholder="m@example.com"></ui-input>
  </div>
</body>
</html>

Examples

With a checkbox or switch

Clicking the label text toggles the control, like a native label.

Previewindex.html
Accept terms and conditions
Airplane mode
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Label demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-stack {
      display: grid;
      gap: calc(var(--spacing) * 4);
    }
    .demo-row-tight {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: calc(var(--spacing) * 2);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/checkbox.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/switch.html"></import-element>

  <div class="demo-stack">
    <div class="demo-row-tight">
      <ui-checkbox id="label-terms"></ui-checkbox>
      <ui-label for="label-terms">Accept terms and conditions</ui-label>
    </div>
    <div class="demo-row-tight">
      <ui-switch id="label-airplane"></ui-switch>
      <ui-label for="label-airplane">Airplane mode</ui-label>
    </div>
  </div>
</body>
</html>

With a native control

Previewindex.html
Notes
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Label demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-stack-tight {
      display: grid;
      gap: calc(var(--spacing) * 2);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>

  <div class="demo-stack-tight">
    <ui-label for="label-native">Notes</ui-label>
    <textarea id="label-native" rows="2"></textarea>
  </div>
</body>
</html>

In a field

Inside a ui-field, the label is a plain child placed before the control.

Previewindex.html
Company
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Label demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-narrow {
      width: min(100%, calc(var(--spacing) * 88));
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/field.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>

  <div class="demo-narrow">
    <ui-field>
      <ui-label for="label-company">Company</ui-label>
      <ui-input id="label-company" autocomplete="organization"></ui-input>
    </ui-field>
  </div>
</body>
</html>

In a form

The label is not submitted. It names the controls whose values are, and clicking it works inside a form the same way.

Previewindex.htmllabel-form-demo.html
City
Send me the newsletter
Save
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Label demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/checkbox.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>

  <import-element from="./label-form-demo.html"></import-element>
  <label-form-demo></label-form-demo>
</body>
</html>
<custom-element name="label-form-demo">
  <script>
    const result = useState({ text: "Submit the form to see its data." });

    function submit(event) {
      event.preventDefault();
      const entries = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`);
      result.text = entries.join("\n") || "(nothing submitted)";
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 4);
      width: 100%;
      max-width: calc(var(--spacing) * 80);
    }
    form {
      display: grid;
      gap: calc(var(--spacing) * 3);
      justify-items: start;
    }
    .row {
      display: flex;
      align-items: center;
      gap: calc(var(--spacing) * 2);
    }
    ui-input {
      width: 100%;
    }
    output {
      display: block;
      padding: calc(var(--spacing) * 3);
      border-radius: var(--radius-md);
      background: var(--muted);
      font-family: var(--font-mono);
      font-size: var(--text-xs);
      white-space: pre-wrap;
    }
  </style>

  <form onsubmit="submit(event)">
    <ui-label for="label-form-city">City</ui-label>
    <ui-input id="label-form-city" name="city" value="Lisbon"></ui-input>
    <div class="row">
      <ui-checkbox id="label-form-news" name="newsletter" value="yes"></ui-checkbox>
      <ui-label for="label-form-news">Send me the newsletter</ui-label>
    </div>
    <ui-button type="submit">Save</ui-button>
  </form>
  <output><slot name="#result.text"></slot></output>
</custom-element>

API

Attributes

Attribute Type Default Description
for string Id of the control to label, looked up in the label's own document or shadow root.

Parts

Part Slot Renders
ui-label (default) The label text, and optionally an icon, in a <span> that handles the click. Inside ui-field it is a plain child.

The default slot takes any content.

CSS parts

Part Description
label The <span> around the slotted text. It handles the click.

CSS states

State Description
:state(attr-for) Set while for is present. The label then shows a pointer cursor.

Accessibility

ui-label is not a native <label>, so the browser does not link it to the control. Instead, when the label is connected it sets the target's aria-label to the label text, unless the target already has an aria-label. The text is copied once. Changing the label text later does not update it.

Only the label text responds to clicks. The target must exist when the label is connected and be in the same document or shadow root.

Edit this page on GitHub · View as Markdown