Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/components/time-field/src/time-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ export class TimeField extends LocaleMixin(FormControlMixin(ScopedElementsMixin(
`;
}

/**
* Renders hour options (00–23) using hourStep, applies min/max constraints, and marks the selected hour with aria-selected.
* Can be overridden.
*/
renderHours(): TemplateResult[] {
let hours = Array.from({ length: 24 / this.hourStep }, (_, i) => i * this.hourStep);

Expand Down Expand Up @@ -314,6 +318,10 @@ export class TimeField extends LocaleMixin(FormControlMixin(ScopedElementsMixin(
);
}

/**
* Renders minute options using minuteStep and marks the selected one with aria-selected.
* Can be overridden.
*/
renderMinutes(): TemplateResult[] {
const minutes = Array.from({ length: 60 / this.minuteStep }, (_, i) => i * this.minuteStep);

Expand Down
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"type": "module",
"main": "index.js",
"customElements": "src/_data/custom-elements/custom-elements.json",
"scripts": {
"build": "wireit",
"lint": "wireit",
Expand Down
2 changes: 1 addition & 1 deletion website/src/categories/components/text-field/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ With these options you can tweak the appearance of the text field in Figma. They
|Placeholder text|`value`|Use placeholder text to give the user a short hint about what they need to input (e.g. a sample value or a short description of the expected format). Placeholder is not a replacement for labels. It's an optional feature that disappears once users begin entering their data. |
|Hint|`boolean`|To turn to hint on or off. Default value is `off`.|
|Input Text |`value`|Will be shown as the value of the text field|
|Label|`value`|Provide users with additional context about button functionality by adding a label, ensuring clarity and ease of use.|
|Label|`value`|Provide users with a clear, concise label of what input you expect in this field.|
|Focus ring|`boolean`|Turn the focus ring option to show the focus state of the text field. Default value is `off`.|

{.ds-table .ds-table-align-top}
Expand Down
60 changes: 60 additions & 0 deletions website/src/categories/components/time-field/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: Time field accessibility
tags: accessibility
eleventyNavigation:
parent: Time field
key: TimeFieldAccessibility
---
<section>

## Keyboard interactions

The time field supports keyboard navigation for both the input and the dropdown time picker.

<div class="ds-table-wrapper">

|Command| Description |
|-|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|Tab| When focus is outside the time field, moves focus to the input. If focus is on the input, pressing Tab moves to the dropdown button, then to the next focusable element. |
|Space/Enter (on button)| Toggles the time picker dropdown when the button has focus. |
|Escape| Closes the time picker dropdown. |
|Arrow Up/Down (in dropdown)| Navigates through the hour or minute options in the currently focused column. |
|Arrow Left/Right (in dropdown)| Switches focus between the hour and minute columns. |
|Enter (in dropdown)| Selects the currently focused time option and closes the dropdown. |

{.ds-table .ds-table-align-top}

</div>

</section>

<section>

## WAI-ARIA

{{ 'aria-attributes' | recurringText }}

<div class="ds-table-wrapper">

|Attribute | Value | Description |
|-|-|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`aria-label`|string| Defines a string that labels the time field. Required when not wrapped with `sl-form-field` and there is no `sl-label` component. |
|`aria-labelledby`|string| References the ID of the element that labels the time field. |
|`aria-required`|boolean| Informs the user that an element is required. When set to ‘true’, screen readers notify users that the element is required. If there is already a `required` attribute added, it is recommended to avoid adding `aria-required`. |
|`aria-disabled`|boolean| Announces the time field as disabled with a screen reader. See note below about difference from the `disabled` attribute. |

{.ds-table .ds-table-align-top}

</div>

**Notes:**

1. The `aria-disabled` should not be used as a one-for-one replacement for the `disabled` attribute because they have different functionalities:

- The `disabled` attribute dims the time field visually, removes it from tab focus, prevents all interactions, and announces it as disabled.
- The `aria-disabled` attribute only announces the disabled state but doesn't prevent interaction or remove focus.

Use `disabled` for true disabled states, use `aria-disabled` only if you need the element to remain in the tab sequence for specific accessibility reasons.

</section>

52 changes: 52 additions & 0 deletions website/src/categories/components/time-field/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Time field code
tags: code
APIdescription: Component to pick and edit a time for many use cases, with accessibility and validation.
eleventyNavigation:
parent: Time field
key: TimeFieldCode
---
<section class="no-heading">

<div class="ds-example">
<sl-time-field
aria-label="A parent‑teacher meeting"
value="16:30"
></sl-time-field>
</div>

<div class="ds-code">

```html
<sl-time-field aria-label="A parent‑teacher meeting" value="16:30"></sl-time-field>
```

</div>

</section>

<ds-install-info link-in-navigation package="time-field"></ds-install-info>

<section>

## Localization

The separator between hours and minutes follows the active locale (via `Intl.DateTimeFormat`).
\
Most locales use a colon `:`, but a few (for example Finnish `fi`) use a dot `.` (e.g. `16.30`).

</section>

<section>

## Supported format

The time field supports the **24-hour** format `HH:MM` (hours `00`–`23`, minutes `00`–`59`).
\
This 24-hour notation is the most common in Europe and is the component default.
\
**12-hour** `AM/PM` format is not supported yet.

</section>

{% include "../component-table.njk" %}
11 changes: 11 additions & 0 deletions website/src/categories/components/time-field/time-field.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"layout": "categories/components/components.njk",
"tags": "time-field",
"componentStatus": "draft",
"componentTagName": [
{
"name": "Time field",
"selector": "sl-time-field"
}
]
}
15 changes: 15 additions & 0 deletions website/src/categories/components/time-field/time-field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: Time field
description: The Time Field allows users to enter a time (HH:MM) by typing or using a dropdown with split hour and minute columns. It supports configurable hour/minute steps with free typing by default, plus an optional strict mode that enforces steps that require matching a stepped option. The control favours predictability (no reordering or injected suggestions), solid keyboard flow, and clear validation.
componentType: form
shortDescription: Precise time input with split-column selection and optional step enforcement.
layout: "categories/components/components.njk"
tags: component
packageName: time-field
storybookCategory: form
eleventyNavigation:
parent: Components
key: Time field
status: new
---

143 changes: 143 additions & 0 deletions website/src/categories/components/time-field/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: Time field usage
tags: usage
eleventyNavigation:
parent: Time field
key: TimeFieldUsage
---

<section class="no-heading">

<div class="ds-example">
<sl-time-field
aria-label="Lesson start time"
></sl-time-field>
</div>

<div class="ds-code">

```html
<sl-time-field aria-label="Lesson start time"></sl-time-field>
```

</div>

</section>

<section>

## When to use

The following guidance describes when to use the time field component.

### Precise input and fast selection
Use the Time Field when the task requires an exact clock time and you want to accelerate entry with predictable stepped options. The split-column dropdown provides quick, scannable choices for common schedules (e.g., every 5/10/15/30 minutes) while still allowing direct typing. This is ideal for meetings, bookings, reminders, and any workflow where consistent, granular time values matter.

### Keyboard navigation

Use the Time Field when forms must support speed and accessibility for keyboard-centric users. The input accepts typed values, opens the dropdown via keyboard, and allows navigation of hour/minute columns with the arrow keys. This is especially useful in dense forms, operational tooling, or power-user interfaces where mouse interaction is secondary.

</section>


<section>

## When not to use
Time fields may not be the best choice in the following scenarios:

### Relative timeframes
Avoid the Time Field when only approximate periods or relative notions of time are needed (e.g., “morning,” “after lunch,” “end of day”). In these cases, prefer radios, a segmented control, or a select that reflects coarse time choices without implying exact HH:MM precision.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add for duration; so don't use this when you set the duration of a test for example, where you could select 01:30 if you want it to be 90 minutes long.

</section>


<section>

## Anatomy

<div class="ds-table-wrapper">

|Item|Name| Description | Optional|
|-|-|-|-|
|1|Container |Wraps input and trigger, provides focus ring and error visuals. |no|
|2|Input |Editable time field for time entry. |no|
|3|Placeholder |Hint text when no value is set (e.g., “HH:MM”). |no|
|4|Icon Button |Opens the dropdown. |no|

{.ds-table .ds-table-align-top}

</div>

</section>


<section>

## States

- **Idle:** Empty, showing the placeholder, or with a formatted time as the value.
- **Hover:** Visual hover effects that show the user that the field is interactive.
- **Focus:** Display the focus ring. It's shown when the field is active by clicking or keyboard navigation.
- **Invalid:** When an incorrect value is entered, the field is styled to indicate the error.
- **Disabled:** Non-interactive, muted.

</section>


<section>

## Figma Options

With these options you can tweak the appearance of the time field in Figma. They are available in the Design Panel so you can compose the time field to exactly fit the user experience need for the use case you are working on.

<div class="ds-table-wrapper">

|Item|Options|Description|
|-|-|-|
|Open|`on` or `off`|The time field is available in two sizes. If not specified the default value is `md` (medium).|
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no property "open" that i can see, the description is for the size but the options aren't
Very confusing line 😁

|Variant|`default` `valid` `invalid`| When you're working on a scenario where you show what happens when a field is skipped or filled in incorrectly you can choose a different variant to show this.|
|Size|`md` `lg`|The time field is available in two sizes. If not specified the default value is `md` (medium).|
|Placeholder|`on` or `off`|If the setting is enabled, the placeholder will be visible, whereas if it is disabled, the user's input will be displayed. Default value is `off`.|
|Text|`value`|Use placeholder text to give the user a short hint about what they need to input (e.g. a sample value or a short description of the expected format). Placeholder is not a replacement for labels. It's an optional feature that disappears once users begin entering their data. |
|Placeholder text|`value`|Use placeholder text to give the user a short hint about what they need to input (e.g. a sample value or a short description of the expected format). Placeholder is not a replacement for labels. It's an optional feature that disappears once users begin entering their data. |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same description as for Text

|Label|`value`|Provide users with a clear, concise label of what input you expect in this field.|

{.ds-table .ds-table-align-top}

</div>

</section>


<section>

## Behavior

### Free Typing (Default)
Users can type any valid HH:MM time, regardless of whether it is a step time. The component validates for format and parsability (e.g., two-digit hours/minutes within valid ranges). Step alignment is not enforced unless `enforceSteps` = `true`.

### Stepped Options (Shortcuts)
When steps are configured, the dropdown shows fixed, non-reordering stepped options in the hour and/or minute columns. These options act as shortcuts while typed input remains unrestricted. The dropdown content and order remain unchanged regardless of the user's input.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-reordering stepped options
what do you mean by 'non-reordering'?


### Enforced Steps (Optional)
When `enforceSteps` = `true`, only stepped values are valid. If a user types an invalid time, a validation message appears after the field is blurred (i.e., when the user leaves the field). The typed value remains visible until corrected. The dropdown content and order still do not change.

### Keyboard Navigation
Press Tab to focus the field and start typing or press tab again so the focus moves to the clock icon and then press Enter/Space to open the dropdown. Use the Arrow keys to navigate the hour and minute columns. Typing in the input updates the value; in strict mode, off-step values are validated when the field loses focus. Press Esc to close the dropdown and return focus to the input.

### How to Open the Dropdown
Click on the field or dropdown button, or press Enter/Space when the button with the clock icon has focus.

### Formatting & Validation
The control accepts and normalises valid times as HH:MM. Optional minTime / maxTime constraints may be applied with clear validation messages. When `enforceSteps` = `true`, error text should indicate the required step pattern (e.g., “Select a time in 15-minute steps: 00, 15, 30, 45”).

</section>


<section>

## Related components
- [Text Field](/categories/components/text-field/usage): If you want a free-form text input.
- [Select](/categories/components/select/usage): Selecting from predefined numeric options.

</section>
4 changes: 4 additions & 0 deletions website/src/styles/components/code.scss
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ pre[class*='language-'] {
color: var(--control-color-code-text-warning);
}

.token.class-name {
color: var(--control-color-code-text-warning);
}

.token.important,
.token.bold {
font-weight: 700;
Expand Down
11 changes: 6 additions & 5 deletions website/src/styles/tokens-dark.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* stylelint-disable color-no-hex */
@media (prefers-color-scheme: dark) {
:root {
--component-color-input-field-background: #3366ff14;
Expand Down Expand Up @@ -82,10 +83,10 @@
--control-color-visuals-guide-clickable-area: var(--colors-warning-400-warning);
--control-color-visuals-guide-fill: var(--colors-accent-500-accent);
--control-color-visuals-guide-text: var(--colors-neutral-base-white);
--control-color-code-text-neutral: var(--sl-color-palette-neutral-100);
--control-color-code-text-accent: var(--sl-color-palette-accent-200);
--control-color-code-text-danger: var(--sl-color-palette-danger-200);
--control-color-code-text-warning: var(--sl-color-palette-warning-200);
--control-color-code-text-success: var(--sl-color-palette-success-200);
--control-color-code-text-neutral: var(--sl-color-palette-grey-500);
--control-color-code-text-accent: var(--sl-color-palette-blue-500);
--control-color-code-text-danger: var(--sl-color-palette-red-500);
--control-color-code-text-warning: var(--sl-color-palette-orange-500);
--control-color-code-text-success: var(--sl-color-palette-green-500);
}
}
11 changes: 6 additions & 5 deletions website/src/styles/tokens-light.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* stylelint-disable color-no-hex */
@media (prefers-color-scheme: light) {
:root {
/* colors */
Expand Down Expand Up @@ -83,10 +84,10 @@
--control-color-visuals-guide-clickable-area: var(--colors-200-warnng);
--control-color-visuals-guide-fill: var(--colors-accent-base-accent);
--control-color-visuals-guide-text: var(--colors-neutral-base-white);
--control-color-code-text-neutral: var(--sl-color-palette-neutral-500);
--control-color-code-text-accent: var(--sl-color-palette-accent-500);
--control-color-code-text-danger: var(--sl-color-palette-danger-500);
--control-color-code-text-warning: var(--sl-color-palette-warning-500);
--control-color-code-text-success: var(--sl-color-palette-success-500);
--control-color-code-text-neutral: var(--sl-color-palette-grey-500);
--control-color-code-text-accent: var(--sl-color-palette-blue-500);
--control-color-code-text-danger: var(--sl-color-palette-red-500);
--control-color-code-text-warning: var(--sl-color-palette-orange-500);
--control-color-code-text-success: var(--sl-color-palette-green-500);
}
}
1 change: 1 addition & 0 deletions website/src/ts/scripts/slds-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import '@sl-design-system/tabs/register.js';
import '@sl-design-system/tag/register.js';
import '@sl-design-system/text-area/register.js';
import '@sl-design-system/text-field/register.js';
import '@sl-design-system/time-field/register.js';
import '@sl-design-system/toggle-button/register.js';
import '@sl-design-system/toggle-group/register.js';
import '@sl-design-system/tooltip/register.js';
Expand Down