diff --git a/.changeset/four-suits-design.md b/.changeset/four-suits-design.md
new file mode 100644
index 00000000000..d2692b1c5bb
--- /dev/null
+++ b/.changeset/four-suits-design.md
@@ -0,0 +1,5 @@
+---
+'@spectrum-web-components/number-field': minor
+---
+
+**Added**: slotted visible label to resolve cross-root ARIA issues (Before: `Label` / After: `Label`)
diff --git a/.changeset/funny-dingos-teach.md b/.changeset/funny-dingos-teach.md
new file mode 100644
index 00000000000..ec3d973131a
--- /dev/null
+++ b/.changeset/funny-dingos-teach.md
@@ -0,0 +1,5 @@
+---
+'@spectrum-web-components/field-label': minor
+---
+
+**Added**: field label mixin to insert a label in a component's shadow DOM to resolve cross-root ARIA issues (`export class FormField extends FieldLabelMixin(SpectrumElement, 'label')` to apply mixin with a `label` slot to a class and `this.renderFieldLabel('field_id')` to render a field label for an element with `id="field_id"`)
diff --git a/.changeset/six-dragons-pump.md b/.changeset/six-dragons-pump.md
new file mode 100644
index 00000000000..df9b95a7655
--- /dev/null
+++ b/.changeset/six-dragons-pump.md
@@ -0,0 +1,5 @@
+---
+'@spectrum-web-components/combobox': minor
+---
+
+**Added**: slotted visible label to resolve cross-root ARIA issues (Before: `Label` / After: `Label`)
diff --git a/.changeset/solid-streets-sip.md b/.changeset/solid-streets-sip.md
new file mode 100644
index 00000000000..16036c65792
--- /dev/null
+++ b/.changeset/solid-streets-sip.md
@@ -0,0 +1,5 @@
+---
+'@spectrum-web-components/textfield': minor
+---
+
+**Added**: slotted visible label to resolve cross-root ARIA issues (Before: `Label` / After: `Label`)
diff --git a/.changeset/thick-towns-hammer.md b/.changeset/thick-towns-hammer.md
new file mode 100644
index 00000000000..eb01db1d2d4
--- /dev/null
+++ b/.changeset/thick-towns-hammer.md
@@ -0,0 +1,5 @@
+---
+'@spectrum-web-components/color-field': minor
+---
+
+**Added**: slotted visible label to resolve cross-root ARIA issues (Before: `Label` / After: `Label`)
diff --git a/packages/color-field/README.md b/packages/color-field/README.md
index 52dcb6dee14..116192695a9 100644
--- a/packages/color-field/README.md
+++ b/packages/color-field/README.md
@@ -27,15 +27,78 @@ import { ColorField } from '@spectrum-web-components/color-field';
The color field consists of several key parts:
+- **Label**: Visual or visually hidden text that describes the color field to the user
- **Input field**: The main text input area where users can type color values
- **Color handle**: An optional visual indicator showing the current color (when `view-color` attribute is enabled)
- **Validation feedback**: Visual indicators for valid and invalid color inputs
- **Size variations**: Different size options to match your design requirements
```html
-
+Background color
```
+#### Label
+
+A color field must have a label in order to be accessible. A label can be provided either via the default slot, or via the `label` attribute, for a hidden label that can be read by assistive technology. If no label is provided, the component will have a visually hidden label with the text `color`.
+
+
+Visible slotted label
+
+
+```html
+Text color
+```
+
+
+Visually hidden label attribute
+
+
+```html
+
+```
+
+
+
+
+### Options
+
+#### Sizes
+
+
+Small
+
+
+```html
+
+```
+
+
+Medium
+
+
+```html
+
+```
+
+
+Large
+
+
+```html
+
+```
+
+
+Extra Large
+
+
+```html
+
+```
+
+
+
+
### Options
#### Sizes
@@ -45,7 +108,11 @@ The color field consists of several key parts:
```html
-
+
```
@@ -53,7 +120,11 @@ The color field consists of several key parts:
```html
-
+
```
@@ -62,7 +133,11 @@ The color field consists of several key parts:
```html
-
+
```
@@ -71,7 +146,11 @@ The color field consists of several key parts:
```html
-
+
```
@@ -82,7 +161,7 @@ The color field consists of several key parts:
When `view-color` is true, the color handle will be rendered. This is useful for development and debugging purposes.
```html
-
+Icon color
```
#### Quiet
@@ -90,7 +169,7 @@ When `view-color` is true, the color handle will be rendered. This is useful for
A quiet color field provides a more subtle appearance:
```html
-
+Icon color
```
### States
@@ -100,7 +179,7 @@ A quiet color field provides a more subtle appearance:
The default state of the color field, ready for user input:
```html
-
+Icon color
```
#### Read Only
@@ -108,7 +187,7 @@ The default state of the color field, ready for user input:
A readonly color field that displays the color value but prevents user modification:
```html
-
+Icon color
```
#### Invalid Input
@@ -116,7 +195,7 @@ A readonly color field that displays the color value but prevents user modificat
If the input value is not a valid color, `` will not accept it and may show validation feedback:
```html
-
+Icon color
```
### Behaviors
@@ -134,7 +213,7 @@ For a complete list of supported color formats, see the [ColorController documen
A hexadecimal color is specified with: `#RRGGBB`. `RR` (red), `GG` (green) and `BB` (blue) are hexadecimal integers between `00` and `FF` specifying the intensity of the color.
```html
-
+
```
@@ -144,7 +223,7 @@ A hexadecimal color is specified with: `#RRGGBB`. `RR` (red), `GG` (green) and `
Shorthand hexadecimal color values are also supported. `#RGB` is a shorthand for `#RRGGBB`. In the shorthand form, `R` (red), `G` (green), and `B` (blue) are hexadecimal characters between `0` and `F`. Each character is repeated to create the full 6-digit color code. For example, `#123` would expand to `#112233`.
```html
-
+
```
@@ -154,7 +233,11 @@ Shorthand hexadecimal color values are also supported. `#RGB` is a shorthand for
An RGB color value is specified with: rgb(red, green, blue). Each parameter defines the intensity of the color with a value between 0 and 255.
```html
-
+
```
@@ -164,7 +247,11 @@ An RGB color value is specified with: rgb(red, green, blue). Each parameter defi
An RGBA color value is specified with: `rgba(red, green, blue, alpha)`. The `alpha` parameter is a number between 0.0 (fully transparent) and 1.0 (fully opaque).
```html
-
+
```
@@ -174,7 +261,11 @@ An RGBA color value is specified with: `rgba(red, green, blue, alpha)`. The `alp
An HSL color value is specified with: hsl(hue, saturation, lightness). Hue is a degree on the color wheel from 0 to 360. 0 is red, 120 is green, and 240 is blue. Saturation and lightness are percentages.
```html
-
+
```
@@ -184,7 +275,11 @@ An HSL color value is specified with: hsl(hue, saturation, lightness). Hue is a
An HSV color value is specified with: hsv(hue, saturation, value). Hue is a degree on the color wheel from 0 to 360. 0 is red, 120 is green, and 240 is blue. Saturation and value are percentages.
```html
-
+
```
diff --git a/packages/color-field/stories/color-field-sizes.stories.ts b/packages/color-field/stories/color-field-sizes.stories.ts
index ec408601a77..9d514700963 100644
--- a/packages/color-field/stories/color-field-sizes.stories.ts
+++ b/packages/color-field/stories/color-field-sizes.stories.ts
@@ -10,8 +10,6 @@
* governing permissions and limitations under the License.
*/
import { TemplateResult } from '@spectrum-web-components/base';
-
-import '@spectrum-web-components/field-label/sp-field-label.js';
import '@spectrum-web-components/help-text/sp-help-text.js';
import { ColorFieldMarkup } from './template.js';
diff --git a/packages/combobox/README.md b/packages/combobox/README.md
index 4b2dee37ca0..ee2ff82a887 100644
--- a/packages/combobox/README.md
+++ b/packages/combobox/README.md
@@ -25,28 +25,111 @@ import { Combobox } from '@spectrum-web-components/combobox';
### Anatomy
-Combobox options are presented as a popup menu.
-Menu items can be provided via markup as `` children, or by assigning an array to the `options` property of an ``.
+A combobox has the following:
-#### Menu items via the `options` property
+- a label
+- a textfield for the user to type in, with optional placeholder text
+- a picker button to open the listbox
+- a listbox of options
+- optional help text
-Instead of providing `` children, you can assign an array of `ComboboxOptions` to the `options` property, and `` will create matching menu items:
+```html
+
+ Color
+ Red
+ Green
+ Blue
+
+```
+
+#### Label
+
+A combobox must have a label in order to be accessible. A label can be provided either via the `field-label` slot, or via the `label` attribute, for a hidden label that can be read by assistive technology.
+
+
+Visible slotted label
+
+
+```html
+
+ Color
+ Red
+ Green
+ Blue
+
+```
+
+
+Visually hidden label attribute
+
+
+```html
+
+ Red
+ Green
+ Blue
+
+```
+
+
+
+
+#### Placeholder
-```html-no-demo
+Use the `placeholder` attribute to include placeholder text.
+
+**Note**: Placeholder text should not be used as a replacement for a label or help text.
+
+```html
+
+ Red
+ Green
+ Blue
+
+```
+
+#### Listbox options
+
+Listbox options can be added as slotted `sp-menu-item` children or as an array of `ComboboxOptions`. They can also be loaded dynamically.
+
+
+Slotted children
+
+
+You can use the default slot to provide `` children.
+
+```html
+
+ Red
+ Green
+ Blue
+
+```
+
+
+Array of options
+
+
+You can assign an array of `ComboboxOptions` to the `options` property, and `` will create matching menu items
+
+```html
```
-#### Menu items via dynamic options
+
+Dynamic options
+
+
+When you replace the `dynamic` Array, or add/remove `` children, the `` will detect that change and update its popup menu contents.
-When you replace the `options` Array, or add/remove `` children, the `` will detect that change and update its popup menu contents.
For example, using [Lit](https://lit.dev/):
```ts
@@ -62,6 +145,9 @@ mutate() {
}
```
+
+
+
### Options
#### Sizes
@@ -120,8 +206,8 @@ mutate() {
### Quiet
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -140,8 +226,8 @@ The suggested popup menu items will remain the same regardless of the currently-
Whenever the currently-typed input exactly matches the `value` of a popup menu item, that item is automatically selected.
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -155,8 +241,8 @@ Whenever the currently-typed input exactly matches the `value` of a popup menu i
The popup menu items are filtered to only those completing the currently-input value.
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -170,15 +256,15 @@ The popup menu items are filtered to only those completing the currently-input v
```html
-Color
-
+
+ ColorRedGreenBlue
-Color
-
+
+ ColorRedGreenBlue
@@ -190,8 +276,8 @@ The popup menu items are filtered to only those completing the currently-input v
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -206,8 +292,8 @@ The popup menu items are filtered to only those completing the currently-input v
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -222,12 +308,12 @@ The popup menu items are filtered to only those completing the currently-input v
#### Provide a label
A combobox must be labeled.
-Typically, you should render a visible label via ``.
+Typically, you should render a visible label via the `field-label` slot.
For exceptional cases, provide an accessible label via the `label` attribute.
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -245,8 +331,8 @@ See [help text](../help-text) and [tooltip](../tooltip) for more information.
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -259,8 +345,8 @@ See [help text](../help-text) and [tooltip](../tooltip) for more information.
```html
-Color
-
+
+ ColorRedGreenBlue
@@ -274,8 +360,8 @@ See [help text](../help-text) and [tooltip](../tooltip) for more information.
```html
-Color
-
+
+ Color
Color options, such as red, green, or blue.
diff --git a/packages/combobox/package.json b/packages/combobox/package.json
index 6be53918fe4..b18fb1a4a6f 100644
--- a/packages/combobox/package.json
+++ b/packages/combobox/package.json
@@ -51,21 +51,11 @@
"!stories/",
"!test/"
],
- "keywords": [
- "design-system",
- "spectrum",
- "adobe",
- "adobe-spectrum",
- "web components",
- "web-components",
- "lit-element",
- "lit-html",
- "component",
- "css"
- ],
+ "types": "./src/index.d.ts",
"dependencies": {
"@spectrum-web-components/action-button": "1.9.0",
"@spectrum-web-components/base": "1.9.0",
+ "@spectrum-web-components/field-label": "1.9.0",
"@spectrum-web-components/icon": "1.9.0",
"@spectrum-web-components/icons-ui": "1.9.0",
"@spectrum-web-components/menu": "1.9.0",
@@ -74,9 +64,21 @@
"@spectrum-web-components/popover": "1.9.0",
"@spectrum-web-components/progress-circle": "1.9.0",
"@spectrum-web-components/reactive-controllers": "1.9.0",
+ "@spectrum-web-components/shared": "1.9.0",
"@spectrum-web-components/textfield": "1.9.0"
},
- "types": "./src/index.d.ts",
+ "keywords": [
+ "design-system",
+ "spectrum",
+ "adobe",
+ "adobe-spectrum",
+ "web components",
+ "web-components",
+ "lit-element",
+ "lit-html",
+ "component",
+ "css"
+ ],
"customElements": "custom-elements.json",
"sideEffects": [
"./sp-*.js",
diff --git a/packages/combobox/src/Combobox.ts b/packages/combobox/src/Combobox.ts
index 2495b1537aa..edfd903e1ed 100644
--- a/packages/combobox/src/Combobox.ts
+++ b/packages/combobox/src/Combobox.ts
@@ -36,6 +36,7 @@ import '@spectrum-web-components/picker-button/sp-picker-button.js';
import '@spectrum-web-components/progress-circle/sp-progress-circle.js';
import '@spectrum-web-components/popover/sp-popover.js';
import { Textfield } from '@spectrum-web-components/textfield';
+import { FieldLabelMixin } from '@spectrum-web-components/field-label/src/FieldLabelMixin.js';
import type { Tooltip } from '@spectrum-web-components/tooltip';
import chevronStyles from '@spectrum-web-components/icon/src/spectrum-icon-chevron.css.js';
@@ -53,7 +54,7 @@ export type ComboboxOption = {
* @slot - Supply Menu Item elements to the default slot in order to populate the available options
* @slot tooltip - Tooltip to to be applied to the the Picker Button
*/
-export class Combobox extends Textfield {
+export class Combobox extends FieldLabelMixin(Textfield, 'field-label') {
public static override get styles(): CSSResultArray {
return [...super.styles, styles, chevronStyles];
}
@@ -348,44 +349,25 @@ export class Combobox extends Textfield {
super.onBlur(event);
}
- protected renderVisuallyHiddenLabels(): TemplateResult {
- /**
- * appliedLabel corresponds to `
`;
};
export const lightDOM = (): TemplateResult => {
return html`
-
- Fruit
-
+ Fruit
${fruits.map(
(fruit) => html`
@@ -167,13 +160,11 @@ export const lightDOM = (): TemplateResult => {
`
)}
-
- Countries
-
+ Countries
${countries.map(
(country) => html`
@@ -188,7 +179,6 @@ export const lightDOM = (): TemplateResult => {
export const withTooltip = (): TemplateResult => {
return html`
@@ -208,24 +198,21 @@ export const withTooltip = (): TemplateResult => {
export const withFieldLabel = (): TemplateResult => {
return html`
- Pick something
-
+
+ Pick something
+
`;
};
export const withLabelAttribute = (): TemplateResult => {
return html`
-
+
`;
};
export const withHelpText = (): TemplateResult => {
return html`
-
+
These are fruits found in the game "Animal Crossing: New Leaf".
@@ -250,15 +237,15 @@ class ControlledCombo extends LitElement {
override render(): TemplateResult {
return html`
-
- Retirement age (try entering a non-number)
-
+ >
+
+ Retirement age (try entering a non-number)
+
+
`;
}
diff --git a/packages/combobox/stories/index.ts b/packages/combobox/stories/index.ts
index 776b306ce3b..525a69eeab3 100644
--- a/packages/combobox/stories/index.ts
+++ b/packages/combobox/stories/index.ts
@@ -16,7 +16,6 @@ import {
} from '@spectrum-web-components/base';
import { Combobox, ComboboxOption } from '@spectrum-web-components/combobox';
import '@spectrum-web-components/combobox/sp-combobox.js';
-import '@spectrum-web-components/field-label/sp-field-label.js';
import { spreadProps } from '../../../test/lit-helpers';
export type StoryArgs = {
@@ -49,15 +48,15 @@ const handleInput =
export const ComboboxMarkup = (args: StoryArgs): TemplateResult => {
return html`
- Where do you live?
+ >
+ Where do you live?
+
`;
};
diff --git a/packages/combobox/test/combobox-a11y.test.ts b/packages/combobox/test/combobox-a11y.test.ts
index 8d76add3bbc..db9cb21db12 100644
--- a/packages/combobox/test/combobox-a11y.test.ts
+++ b/packages/combobox/test/combobox-a11y.test.ts
@@ -44,7 +44,7 @@ describe('Combobox accessibility', () => {
await elementUpdated(el);
await expect(el).to.be.accessible();
});
- it('renders accessibly with ', async () => {
+ it('renders accessibly with slotted label', async () => {
const test = await fixture(html`
${withFieldLabel()}
`);
@@ -64,7 +64,7 @@ describe('Combobox accessibility', () => {
await expect(el).to.be.accessible();
});
- it('manages its "name" value with ', async () => {
+ it('manages its "name" value with slotted label', async () => {
const test = await fixture(html`
${withFieldLabel()}
`);
diff --git a/packages/field-label/field-label-mixin.md b/packages/field-label/field-label-mixin.md
new file mode 100644
index 00000000000..eb9da60edff
--- /dev/null
+++ b/packages/field-label/field-label-mixin.md
@@ -0,0 +1,143 @@
+## Overview
+
+The `FieldLabelMixin` provides a consistent way to render accessible, visually integrated labels for form controls within custom elements. It handles label visibility, required field indicators, disabled states, and label positioning.
+
+Spectrum Web Components leverages the `FieldLabelMixin` to power elements like `[sp-textfield](./textfield)`, `[sp-combobox](./combobox)`, `[sp-number-field](./number-field)`, and `[sp-color-field](./color-field)`.
+
+### Usage
+
+[](https://www.npmjs.com/package/@spectrum-web-components/field-label)
+[](https://bundlephobia.com/result?p=@spectrum-web-components/field-label)
+
+```zsh
+yarn add @spectrum-web-components/field-label
+```
+
+Import `FieldLabelMixin` via:
+
+```js
+import { FieldLabelMixin } from '@spectrum-web-components/field-label/src/FieldLabelMixin.js';
+```
+
+### Anatomy
+
+`FieldLabelMixin` mixes several properties and a render method into your class:
+
+- `disabled`: whether the label appears in a disabled state
+- `required`: whether to display a required indicator (asterisk icon)
+- `sideAligned`: controls label positioning (`'start'` | `'end'` | `undefined`)
+- `slotHasContent`: whether the label slot contains content (inherited from `ObserveSlotText`)
+- `renderFieldLabel(fieldId?: string)`: a method that returns a `TemplateResult` with the label element. The `fieldId` parameter must match the `id` attribute of your form control element to ensure proper label association for accessibility
+
+The mixin accepts optional parameters:
+
+- `slotName`: the name of the slot to observe for label content (defaults to unnamed/default slot)
+- `excludedSelectors`: an array of selectors to exclude when observing slot content
+
+### Options
+
+#### Default slotted label
+
+To add a field label to your custom element, apply the mixin and call `renderFieldLabel()` in your render method. Pass the ID of your form control element as the argument to ensure proper accessibility association:
+
+```js
+import { SpectrumElement, html } from '@spectrum-web-components/base';
+import { FieldLabelMixin } from '@spectrum-web-components/field-label/src/FieldLabelMixin.js';
+import { Focusable } from '@spectrum-web-components/shared/src/focusable.js';
+
+export class MyInput extends FieldLabelMixin(Focusable) {
+ render() {
+ return html`
+ ${this.renderFieldLabel('my-input-field')}
+
+ `;
+ }
+}
+```
+
+#### Named slotted label
+
+To use a named slot for the label content, pass the slot name as a parameter to the mixin:
+
+```js
+export class MyCombobox extends FieldLabelMixin(MyBaseClass, 'field-label') {
+ render() {
+ return html`
+ ${this.renderFieldLabel('combo-input')}
+
+
+
+
+ `;
+ }
+}
+```
+
+This allows consumers to provide label content via:
+
+```html
+
+ Choose an option
+
+```
+
+#### Side-aligned labels
+
+Use the `sideAligned` property to position labels inline with the form control:
+
+```js
+export class MyField extends FieldLabelMixin(Focusable) {
+ // The mixin already provides sideAligned property
+ // It can be set via attribute: side-aligned="start" or side-aligned="end"
+
+ render() {
+ return html`
+ ${this.renderFieldLabel('field')}
+
+ `;
+ }
+}
+```
+
+Usage:
+
+```html
+Label text
+```
+
+### States
+
+#### Required
+
+When the `required` property is set to `true`, the mixin automatically renders an asterisk icon next to the label:
+
+```js
+export class MyField extends FieldLabelMixin(Focusable) {
+ @property({ type: Boolean, reflect: true })
+ public required = false;
+
+ render() {
+ return html`
+ ${this.renderFieldLabel('field')}
+
+ `;
+ }
+}
+```
+
+#### Disabled
+
+The `disabled` property controls the visual state of the label:
+
+```js
+export class MyField extends FieldLabelMixin(Focusable) {
+ // The mixin already provides disabled property
+
+ render() {
+ return html`
+ ${this.renderFieldLabel('field')}
+
+ `;
+ }
+}
+```
diff --git a/packages/field-label/package.json b/packages/field-label/package.json
index 2a4d2769287..6d4462915d1 100644
--- a/packages/field-label/package.json
+++ b/packages/field-label/package.json
@@ -29,15 +29,23 @@
"development": "./src/FieldLabel.dev.js",
"default": "./src/FieldLabel.js"
},
+ "./src/FieldLabelMixin.js": {
+ "development": "./src/FieldLabelMixin.dev.js",
+ "default": "./src/FieldLabelMixin.js"
+ },
"./src/field-label-overrides.css.js": "./src/field-label-overrides.css.js",
"./src/field-label.css.js": "./src/field-label.css.js",
- "./src/index.js": {
- "development": "./src/index.dev.js",
- "default": "./src/index.js"
+ "./sp-field-label-mixin.js": {
+ "development": "./sp-field-label-mixin.dev.js",
+ "default": "./sp-field-label-mixin.js"
},
"./sp-field-label.js": {
"development": "./sp-field-label.dev.js",
"default": "./sp-field-label.js"
+ },
+ "./src/index.js": {
+ "development": "./src/index.dev.js",
+ "default": "./src/index.js"
}
},
"scripts": {
diff --git a/packages/field-label/src/FieldLabelMixin.ts b/packages/field-label/src/FieldLabelMixin.ts
new file mode 100644
index 00000000000..df23ecbb2b1
--- /dev/null
+++ b/packages/field-label/src/FieldLabelMixin.ts
@@ -0,0 +1,104 @@
+/**
+ * Copyright 2025 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import {
+ CSSResultArray,
+ html,
+ nothing,
+ SpectrumElement,
+ TemplateResult,
+} from '@spectrum-web-components/base';
+import { property } from '@spectrum-web-components/base/src/decorators.js';
+import '@spectrum-web-components/icons-ui/icons/sp-icon-asterisk100.js';
+
+import styles from './field-label.css.js';
+import asteriskIconStyles from '@spectrum-web-components/icon/src/spectrum-icon-asterisk.css.js';
+import { ifDefined } from '@spectrum-web-components/base/src/directives.js';
+import { ObserveSlotText } from '@spectrum-web-components/shared';
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type Constructor = new (...args: any[]) => T;
+
+export declare class FieldLabelMixinInterface {
+ disabled: boolean;
+ required: boolean;
+ sideAligned?: 'start' | 'end';
+ slotHasContent: boolean;
+ manageTextObservedSlot(): void;
+ public renderFieldLabel(fieldId: string): TemplateResult;
+}
+
+/**
+ * @mixin FieldLabelMixin
+ *
+ * provides a consistent way to render accessible, visually integrated labels
+ * for form controls within custom elements. It handles label visibility,
+ * required field indicators, disabled states, and label positioning.
+ *
+ * Spectrum Web Components leverages the `FieldLabelMixin` to power elements
+ * like `sp-textfield`, `sp-combobox`, `sp-number-field`, and `sp-color-field`.
+ *
+ * @param superClass - The base class to mixin.
+ * @param slotName - The name of the slot to observe for label content.
+ * @param excludedSelectors - An array of selectors to exclude when observing slot content.
+ * @returns A constructor for the mixin.
+ *
+ * @slot field-label - Text content of the label.
+ */
+export const FieldLabelMixin = >(
+ superClass: T,
+ slotName?: string,
+ excludedSelectors: string[] = []
+): Constructor & T => {
+ class FieldLabelMixinClass extends ObserveSlotText(
+ superClass,
+ slotName,
+ excludedSelectors
+ ) {
+ public static get styles(): CSSResultArray {
+ return [styles, asteriskIconStyles];
+ }
+
+ @property({ type: Boolean, reflect: true })
+ public disabled = false;
+
+ @property({ type: Boolean, reflect: true })
+ public required = false;
+
+ @property({ type: String, reflect: true, attribute: 'side-aligned' })
+ public sideAligned?: 'start' | 'end';
+
+ public renderFieldLabel(fieldId: string): TemplateResult {
+ return html`
+
+
+ ${this.required
+ ? html`
+
+ `
+ : nothing}
+
+ `;
+ }
+ }
+ return FieldLabelMixinClass as Constructor & T;
+};
diff --git a/packages/number-field/README.md b/packages/number-field/README.md
index 87b56d1c206..0d8b96c14da 100644
--- a/packages/number-field/README.md
+++ b/packages/number-field/README.md
@@ -29,19 +29,55 @@ import { NumberField } from '@spectrum-web-components/number-field';
A number field consists of an input field for numeric values and optional stepper buttons for incrementing and decrementing the value. The stepper UI can be hidden using the `hide-stepper` attribute.
```html
-
+
+ What is the air-speed velocity of an unladen swallow?
+
+```
+
+#### Label
+
+A number field must have a label in order to be accessible. A label can be provided either via the default slot, or via the `label` attribute, for a hidden label that can be read by assistive technology.
+
+
+Visible slotted label
+
+
+```html
+
What is the air-speed velocity of an unladen swallow?
-
+
+```
+
+
+Visually hidden label attribute
+
+
+```html
```
+
+
+
### Options
#### Sizes
@@ -94,16 +130,16 @@ An `` element will process its numeric value with `new Intl.Num
The following example uses the `signDisplay` option to include the plus sign for positive numbers, for example to display an offset from some value. In addition, it always displays a minimum of 1 digit after the decimal point, and allows up to 2 fraction digits. If the user enters more than 2 fraction digits, the result will be rounded.
```html
-Adjust exposure
+>
+ Adjust exposure
+
```
@@ -113,14 +149,14 @@ The following example uses the `signDisplay` option to include the plus sign for
The `style: 'percent'` option can be passed to the `formatOptions` property to treat the value as a percentage. In this mode, the value is multiplied by 100 before it is displayed, i.e. `0.45` is displayed as "45%". The reverse is also true: when the user enters a value, the `change` event will be triggered with the entered value divided by 100. When the percent option is enabled, the default step automatically changes to 0.01 such that incrementing and decrementing occurs by 1%. This can be overridden with the step property.
```html
-Sales tax
+>
+ Sales tax
+
```
@@ -132,9 +168,7 @@ The `style: 'currency'` option can be passed to the `formatOptions` property to
If you need to allow the user to change the currency, you should include a separate dropdown next to the `sp-number-field`. The `sp-number-field` itself will not determine the currency from the user input.
```html
-Transaction amount
+>
+ Transaction amount
+
```
@@ -156,16 +192,16 @@ If you need to allow the user to change the unit, you should include a separate
Note: The unit style is not currently supported in Safari. A [polyfill](https://formatjs.io/docs/polyfills/intl-numberformat/) may be necessary.
```html
-Package width
+>
+ Package width
+
```
@@ -175,15 +211,15 @@ Note: The unit style is not currently supported in Safari. A [polyfill](https://
While `Intl.NumberFormatOptions` does support a [wide range of units](https://tc39.es/proposal-unified-intl-numberformat/section6/locales-currencies-tz_proposed_out.html#sec-issanctionedsimpleunitidentifier), it is possible to encounter units (e.g. the graphics units of `pixel`, `pixels`, `points`, etc.) that are not supported therein. When this occurs, an `` element will attempt to polyfill support for this unit. See the following example delivering `{ style: "unit", unit: "px" }` below:
```html
-Document width in pixels
+>
+ Document width in pixels
+
```
Note: the polyfilling done here is very simplistic and is triggered by supplying options that would otherwise cause the `Intl.NumberFormat()` call to throw an error. Once the unsupporting unit of `px` causes the construction of the object to throw, a back up formatter/parser pair will be created without the supplied unit data. When the `style` is set to `unit`, the `unit` value of will be adopted as the _static_ unit display. This means that neither pluralization or translation will be handled within the `` element itself. If pluralization or translation is important to the delivered interface, please be sure to handle passing those strings into to element via the `formatOptions` property reactively to the value of the element or locale of that page in question.
@@ -197,8 +233,7 @@ The `min` and `max` properties can be used to limit the entered value to a speci
If a valid range is known ahead of time, it is a good idea to provide it to `` so it can optimize the experience. For example, when the minimum value is greater than or equal to zero, it is possible to use a numeric keyboard on iOS rather than a full text keyboard (necessary to enter a minus sign).
```html
-Red value
-
+Red value
```
@@ -211,32 +246,9 @@ If the user types a value that is between two steps and blurs the input, the val
```html
-Step
-
-
-Step + min
-
-
-Step + min + max
-
+Step
+Step + min
+Step + min + max
```
@@ -249,11 +261,7 @@ If the user types a value that is between two steps and blurs the input, the val
The `invalid` attribute indicates that the number field's value is invalid. When set, appropriate ARIA attributes will be automatically applied.
```html
-
- It's one banana, Michael, how much could it cost?
-
-
- Value should be between $0 and $0.3.
-
+>
+ It's one banana, Michael, how much could it cost?
+
+ Value should be between $0 and $0.3.
+
+
```
#### Valid
@@ -279,11 +289,7 @@ The `invalid` attribute indicates that the number field's value is invalid. When
The `valid` attribute indicates that the number field's value is valid.
```html
-
- It's one banana, Michael, how much could it cost?
-
+>
+ It's one banana, Michael, how much could it cost?
+
```
#### Required
@@ -306,10 +314,9 @@ The `valid` attribute indicates that the number field's value is valid.
Use the `required` attribute to indicate a number field value is required. Dictate the validity or invalidity state of the text entry with the `valid` or `invalid` attributes.
```html
-Count
-
-Size
-
+Count
+Count
+Size
```
#### Disabled
@@ -317,8 +324,7 @@ Use the `required` attribute to indicate a number field value is required. Dicta
The `disabled` attribute prevents the number field from receiving focus or events. The number field will appear faded.
```html
-Number of tickets
-
+Number of tickets
```
#### Read-only
@@ -326,8 +332,7 @@ The `disabled` attribute prevents the number field from receiving focus or event
Number fields have a `readonly` attribute for when they’re in the disabled state but still need their labels to be shown. This allows for content to be copied, but not interacted with or changed.
```html
-Number of tickets
-
+Number of tickets
```
### Behaviors
@@ -346,10 +351,9 @@ The input value incrementally increases or decreases by the value of the `step`
The `` component doesn't manage a default value by itself. This means that consumers can set the value of the number-field as an empty string by clearing the input. If we want the number-field to reset to a `default-value` when the user clears the input, we can listen for the `change` event on the number-field component and set its value to the desired `default-value` if the input is empty.
```html
-
+
Default value of this number field is 42
-
-
+