Input Ready
Inputs are used to allow users to provide text input when the expected input is short. Input component has a range of options and supports several text formats including numbers.
You can use native HTML5 validation with Input component via validation properties. This allows you to specify rules like whether a value needs to be filled in, the minimum and maximum length of the data, whether it needs to be a number, and a pattern that it must match.
Examples #
<duet-grid grid-template="medium">
<duet-input
type="text"
required
label="Text input"
caption="Allowed length at most 20 characters"
maxlength="20"
debounce="500"
></duet-input>
</duet-grid>
<script>
// Select the above input component
var input = document.querySelector("duet-input")
// Listen for change events. Use debounce prop to adjust the time to trigger this.
input.addEventListener("duetChange", function (e) {
console.log("Change detected in input:", e.detail)
})
</script> <duet-grid grid-template="medium" grid-template-gap="none">
<duet-paragraph>
This input accepts only numbers and decimal separators. Depending on their locale, users may use
either dot or comma as decimal separator, so it is good to accept both in their input and normalise
on saving.
</duet-paragraph>
<duet-paragraph>
On mobile devices the <code>numeric-keyboard</code> attribute gives a number keypad.
</duet-paragraph>
<duet-paragraph>
In many cases where numeric entry is needed this approach is preferable to using Duet Number Input.
Adjust the <code>pattern</code> attribute as needed (e.g. if decimals are not allowed).
</duet-paragraph>
<duet-grid responsive alignment="form-distribute">
<duet-input
id="number-input"
type="text"
required
label="Number"
caption="You can type only numbers and up to two decimals. Enter number between 1 and 1000"
numeric-keyboard
pattern="^\d+([,\.]\d{1,2})?$"
min="1"
max="1000"
disallow-pattern="[^0-9,\.]"
accessible-live-error="polite"
></duet-input>
<duet-button id="submit-button" variation="primary">Submit</duet-button>
</duet-grid>
</duet-grid>
<script>
const input = document.getElementById("number-input")
const button = document.getElementById("submit-button")
function handleSubmit() {
input.error = ""
if (!input.validity.valid) {
if (input.validity.patternMismatch) {
input.error = "Check input - only numbers with up to two decimals are allowed"
} else if (input.validity.valueMissing) {
input.error = "This is mandatory field - please give a number."
} else if (input.validity.rangeOverflow) {
input.error = `Number must not be larger than ${input.max}`
} else if (input.validity.rangeUnderflow) {
input.error = `Number must not be smaller than ${input.min}`
} else {
input.error = "Unspecified error"
}
}
if (!input.error) {
const value = Number.parseFloat(input.value.replace(",", "."))
alert(`You entered ${value}`)
}
}
button.addEventListener("click", handleSubmit)
</script> <duet-input type="tel" required label="Tel input" placeholder="Placeholder text"></duet-input> <duet-input type="email" required label="Email input" placeholder="example@example.com"></duet-input> <duet-input clear="true" type="search" label="Search input" label-hidden placeholder="Type to search"></duet-input> <duet-input type="password" label="Password" placeholder="Password"></duet-input> <duet-grid grid-template="medium" grid-template-gap="none">
<duet-grid responsive alignment="form-distribute">
<duet-input label="With prefix" field-prefix="$" value="100"></duet-input>
<duet-input label="With suffix" field-suffix="km" input-align="right" value="100"></duet-input>
</duet-grid>
<duet-grid responsive alignment="form-distribute">
<duet-input
label="Monetary"
field-suffix="€"
input-align="right"
caption="Give euro sum, between 1 € and 1000 €"
numeric-keyboard
pattern="^\d+([,\.]\d{1,2})?$"
min="1"
max="1000"
disallow-pattern="[^0-9,\.]"
value="100"></duet-input>
</duet-grid>
<duet-grid-item fill></duet-grid-item>
</duet-grid> <duet-grid grid-template="medium">
<duet-input
variation="button"
label="Add things"
caption="Type in the field and click the "Add" button to add chips."
name="chip-input"
chips
expand
>
<duet-button
slot="button"
fixed
margin="none"
variation="input-button-primary"
>Add</duet-button>
</duet-input>
</duet-grid>
<script>
const input = document.querySelector("duet-input[name=chip-input]")
const button = document.querySelector("duet-button")
button.addEventListener("click", async () => {
const text = input.value?.trim()
const value = text.replace(/\W/g, "-")
if (text) {
if (await input.hasChip({ text, value })) {
input.error = `This thing "${text}" has been added already.`
return
}
const chip = document.createElement("duet-chip")
chip.variation = "input"
chip.value = value
chip.textContent = text
input.addChip(chip)
input.value = ""
input.setFocus()
const chipsNow = await input.getChips()
console.log(chipsNow.map(c => c.value))
}
})
input.addEventListener("duetInput", () => input.error = "")
</script> <duet-input disabled label="Disabled input" value="Input value"></duet-input> <duet-input readonly label="Readonly input" value="Some readonly content"></duet-input> <duet-input error="There’s an error!" label="Error input" value="Input value"></duet-input> <duet-input
tooltip="Hello, I’m a tooltip! To close me you can hit ESC key,
click the close button, or click outside of the tooltip."
placeholder="This input has a tooltip"
label="Tooltip usage with input"
>
</duet-input> <duet-input label="Tooltip usage with top caption" accessible-described-by="caption-for-input" placeholder="placeholder" caption="caption" echo-placeholder> </duet-input>
<duet-input label="Tooltip usage with top caption" accessible-described-by="caption-for-input" placeholder="placeholder" caption="caption"> </duet-input>
<duet-input label="Tooltip usage with top caption" accessible-described-by="caption-for-input" placeholder="placeholder" echo-placeholder> </duet-input>
<duet-input label="Tooltip usage with top caption" accessible-described-by="caption-for-input" placeholder="placeholder"> </duet-input>
<duet-input label="Tooltip usage with top caption" value="inputvalue" accessible-described-by="caption-for-input" placeholder="placeholder" caption="caption" echo-placeholder> </duet-input>
<duet-input label="Tooltip usage with top caption" value="inputvalue" accessible-described-by="caption-for-input" placeholder="placeholder" caption="caption"> </duet-input>
<duet-input label="Tooltip usage with top caption" value="inputvalue" accessible-described-by="caption-for-input" placeholder="placeholder" echo-placeholder> </duet-input>
<duet-input label="Tooltip usage with top caption" value="inputvalue" accessible-described-by="caption-for-input" placeholder="placeholder"> </duet-input>
<duet-card style="max-width: 480px">
<duet-input label="Tooltip usage with top caption" value="inputvalue" accessible-described-by="caption-for-input" placeholder="its a long placeholder" echo-placeholder> </duet-input>
</duet-card> <duet-button id="toggle-edit">Edit</duet-button>
<duet-table variation="plain" class="display">
<table>
<thead>
<tr>
<th>Class</th>
<th class="duet-text-right">Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lorem</td>
<td class="currency-column">
<duet-input
input-align="right"
input-padding="xx-small"
margin="none"
label-hidden
label="Lorem in euros"
disallow-pattern="[^0-9]"
></duet-input>
<span class="euro-label" aria-hidden="true">€</span>
<span class="data">9 876 €</span>
</td>
</tr>
<tr>
<td>Ipsum</td>
<td class="currency-column">
<duet-input
input-align="right"
input-padding="xx-small"
margin="none"
label-hidden
label="Ipsum in euros"
disallow-pattern="[^0-9]"
></duet-input>
<span class="euro-label" aria-hidden="true">€</span>
<span class="data">123 €</span>
</td>
</tr>
</tbody>
</table>
</duet-table>
<style>
duet-table {
width: 40rem;
}
.currency-column {
display: flex;
align-items: center;
justify-content: flex-end;
}
.currency-column span {
padding: 0 0.5rem;
}
.display duet-input,
.display span.euro-label,
.editing span.data {
display: none;
}
</style>
<script>
const table = document.querySelector("duet-table")
const currencyCells = table.querySelectorAll("tbody tr td.currency-column")
const toggle = document.getElementById("toggle-edit")
toggle.addEventListener("click", () => {
const editing = table.classList.contains("editing")
currencyCells.forEach(cell => {
const input = cell.querySelector("duet-input")
const dataSpan = cell.querySelector("span.data")
if (editing) {
dataSpan.textContent = new Intl.NumberFormat(
'fi-FI',
{ style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }
).format(input.value || 0)
} else {
input.value = parseInt(dataSpan.textContent.replace(/\s/g, ""))
}
})
table.classList.toggle("editing")
table.classList.toggle("display")
toggle.textContent = editing ? "Edit" : "Save"
})
</script> <duet-input
type="time"
label="Time input"
placeholder="hh:mm"
echo-placeholder="true"
></duet-input>
<script>
// Select the above date picker component
var input = document.querySelector("duet-input")
input.addEventListener("duetChange", function (e) {
console.log("selected time", e.detail.value)
})
</script> Properties #
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
accessibleActiveDescendant | accessible-active-descendant | Indicates the id of a related component’s visually focused element. | string | undefined |
accessibleAutocomplete | accessible-autocomplete | Indicates in what mode user input completion suggestions are provided. Valid values are: "none", "inline", "list", "both" | string | undefined |
accessibleControls | accessible-controls | Use this prop to add an aria-controls attribute. Use the attribute to indicate the id of a component controlled by this component. | string | undefined |
accessibleDescribedBy | accessible-described-by | Indicates the id or a string of space seperated ids of a component(s) that describes the input. | string | "" |
accessibleDescription | accessible-description | Aria description the button | string | undefined |
accessibleDetails | accessible-details | Aria Details of the component | string | undefined |
accessibleExpanded | accessible-expanded | Expanded state of the element, if needed | string | undefined |
accessibleHasPopup | accessible-has-popup | [DEPRECATED] , use accessiblePopup property instead | string | undefined |
accessibleLabelledBy | accessible-labelled-by | String of id's that indicate alternative labels elements | string | undefined |
accessibleLiveError | accessible-live-error | The aria-live attribute for the error message. When the input is validated on blur, use "off", as using "polite" or "assertive" makes the screen reader read the error message twice. When the input is validated on submit, use "polite", as "off" would leave the messages unread by screen readers. Use "assertive" only in those rare cases when "polite" would leave the error message unread by screen readers. | "assertive" | "off" | "polite" | "polite" |
accessibleOwns | accessible-owns | Indicates the id of a component owned by the input. | string | undefined |
accessiblePopup | accessible-popup | Indicates value of any popup element associated with the input. | string | undefined |
autoComplete | auto-complete | Set the type of automatic completion by the browser. If possible use the standard tokens like "name", "given-name", "family-name", "postal-code", "street-address" etc. The component adds automatically "tel" and "email". If you must disable autocomplete use "off" (but avoid disabling unless absolutely necessary). | string | "on" |
caption | caption | Caption (underneath label) that can be set as a way of adding extra information | string | undefined |
chips | chips | Render a container into which Duet Chip components can be added with the method "addChip". | boolean | false |
clear | clear | If set, the input field will display a clear button that can be accessed with tabbing. | boolean | false |
debounce | debounce | Set the amount of time, in milliseconds, to wait to trigger the duetChange event after each keystroke. | number | 0 |
disabled | disabled | Makes the input component disabled. This prevents users from being able to interact with the input, and conveys its inactive state to assistive technologies. | boolean | false |
disallowPattern | disallow-pattern | A regular expression that matches any characters which should be disallowed. This differs from pattern, as it actively prevents users entering any characters which match the regular expression. e.g. the following will disallow any non-numeric characters [^0-9] | string | null |
echoPlaceholder | echo-placeholder | If form input field has a placeholder text, and user types anything (causing the placeholder text to disappear), settings this to true will "echo" it into the caption slot - this option will be false by default for the next few versions, but will eventually be true by default (scheduled for 4.30.0) | boolean | false |
error | error | Display the input in error state along with an error message. If input is disabled or readonly, error is ignored. | string | "" |
expand | expand | Expands the input to fill 100% of the container width. | boolean | false |
fieldPrefix | field-prefix | Text to show before the start (left) of the input's editable area. Also used in accessible labelling. | string | undefined |
fieldSuffix | field-suffix | Text to show after the end (right) of the input's editable area. Also used in accessible labelling. | string | undefined |
icon | icon | Icon to display on the right side (from Duet’s icons). Example: "form-location" | string | undefined |
identifier | identifier | Adds a unique identifier for the input. | string | undefined |
inputAlign | input-align | Alignment of the input element | "left" | "right" | "left" |
inputPadding | input-padding | Padding of the input element. Use this only in special cases, never with regular forms. Setting this to other than the default will not work properly with button variation or together with icons. | "medium" | "small" | "x-small" | "xx-small" | "medium" |
label | label | Label for the input. | string | "Label" |
labelColor | label-color | Color of the label. | string | undefined |
labelHidden | label-hidden | Visually hide the label, but still show it to screen readers. | boolean | false |
labelWeight | label-weight | Font weight of the label. | "normal" | "semi-bold" | undefined |
margin | margin | Controls the margin of the component. | "auto" | "none" | "auto" |
max | max | When max is set and getting validity, the value of the input is interpreted as a number - possible comma as decimal seprator changed to dot and the value then parsed as float - and compared to max and validity set accordingly. This simulates the behavior of max attribute of input[type=number]. | number | undefined |
maxlength | maxlength | Use maxlength to specify the maximum length of the value that can be entered. | number | undefined |
min | min | When min is set and getting validity, the value of the input is interpreted as a number - possible comma as decimal seprator changed to dot and the value then parsed as float - and compared to min and validity set accordingly. This simulates the behavior of min attribute of input[type=number]. | number | undefined |
minlength | minlength | Use minlength to specify the minimum length of the value that can be entered. | number | undefined |
name | name | Name of the input. | string | undefined |
numericKeyboard | numeric-keyboard | Enable numeric keyboard for the input. Note that on iOS the numeric keyboard does not contain minus. | boolean | false |
pattern | pattern | A regular expression to check the value against. Please note that this uses native HTML5 pattern validation. | string | undefined |
placeholder | placeholder | Hint text to display. | string | undefined |
readonly | readonly | Makes the input component not mutable, meaning the user cannot edit the control. The user can otherwise interact, e.g. by selecting the content and copying it. Due to differences in different browsers' and screen readers' implementations it is strongly advisable to use this only with type="text". | boolean | false |
required | required | Set whether the input is required or not. Please note that this is necessary for accessible inputs when the user is required to fill them. When using this property you need to also set “novalidate” attribute to your form element to prevent browser from displaying its own validation errors. | boolean | false |
role | role | Defines a specific role attribute for the input. | string | undefined |
theme | theme | Theme of the input. | "" | "default" | "turva" | "" |
tooltip | tooltip | Tooltip to display next to the label of the input. | string | "" |
tooltipDirection | tooltip-direction | With direction setting you can force the tooltip to always open towards left or right instead of automatically determining the direction. | "auto" | "left" | "right" | "auto" |
type | type | Type of the input. | "email" | "password" | "search" | "tel" | "text" | "time" | "text" |
validity | validity | This is a proxy for the enclosed native inputs validity | ValidityState | undefined |
value | value | Value of the input. | string | undefined |
variation | variation | Variation of button. Use "button" to render a button element next to the input, "button-left" to render a button on the left edge of the input, and "revealable" to render a button that reveals the password. | "button" | "button-left" | "default" | "revealable" | "default" |
Events #
| Event | Description | Type |
|---|---|---|
duetBlur | Emitted when the input loses focus. | CustomEvent<{ originalEvent?: Event; value: string; component: "duet-input"; }> |
duetChange | Emitted when the value has changed. | CustomEvent<{ originalEvent?: Event; value: string; component: "duet-input"; }> |
duetClear | Emitted when the input is cleared. | CustomEvent<{ originalEvent?: Event; value: string; component: "duet-input"; }> |
duetFocus | Emitted when the input has focus. | CustomEvent<{ originalEvent?: Event; value: string; component: "duet-input"; }> |
duetInput | Emitted when a keyboard input ocurred. | CustomEvent<{ originalEvent?: Event; value: string; component: "duet-input"; }> |
Methods #
addChip(chip: HTMLDuetChipElement) => Promise<void> #
Add a chip to the input. Property "chips" must be true.
Parameters #
| Name | Type | Description |
|---|---|---|
chip | HTMLDuetChipElement |
Returns #
Type: Promise<void>
clearChips() => Promise<void> #
Remove all chips from the input.
Returns #
Type: Promise<void>
clearInput() => Promise<void> #
Reset the cursor position on the native element
input.resetCursor().
Returns #
Type: Promise<void>
getChips() => Promise<HTMLDuetChipElement[]> #
Get all chips from the input.
Returns #
Type: Promise<HTMLDuetChipElement[]>
hasChip({ value, text }: { value: string; text: string; }) => Promise<boolean> #
Does the input have a chip with the given value and text.
Parameters #
| Name | Type | Description |
|---|---|---|
__0 | { value: string; text: string; } |
Returns #
Type: Promise<boolean>
resetCursor() => Promise<void> #
Reset the cursor position on the native element
input.resetCursor().
Returns #
Type: Promise<void>
setFocus(options?: FocusOptions) => Promise<void> #
Sets focus on the specified duet-input. Use this method instead of the global
input.focus().
Parameters #
| Name | Type | Description |
|---|---|---|
options | FocusOptions |
Returns #
Type: Promise<void>
Slots #
| Slot | Description |
|---|---|
"tooltip" | Use to place a tooltip alongside the label. |
Usage #
This section includes guidelines for designers and developers about the usage of this component in different contexts.
When to use #
- To allow users to provide text input where the expected input is short.
When not to use #
- When the expected text input is long. Use textarea component instead.
- To let user specify a numeric value in steps. Use number input instead.
Variations #
This section describes the different component variations, their purpose, and when to use each variation.
| Variation | Purpose |
|---|---|
button | Renders a button next to the input field. This variation suits well for single field forms like a search form. |
revealable | Renders an eye icon button inside a password field. The button allows the user to reaveal and hide the password in the field. This variation has better accessibility and usability than plain password field and its use is encouraged always with password fields. |
Types #
This section describes the different input types, their purpose, and when to use each type.
| Type | Purpose |
|---|---|
text | Text input type is the default and most common type. Displayed as a single-line text field. Line-breaks are automatically removed from the input value. |
email | A field for editing an email address. Looks similar to a text input, but has validation parameters and relevant keyboard for devices with dynamic keyboards. |
password | A single-line text field where the value is obscured. This type will alert user if a site is not secure. |
tel | A field for entering a telephone number. Displays a number keypad on mobile devices. |
search | A single-line text field for entering search strings. May include a delete icon in supporting browsers that is used to clear the field. |
Accessibility #
This component has been validated to meet the WCAG 2.1 AA accessibility guidelines. You can find additional information regarding accessibility of this component below.
- Input components uses a basic HTML
<input>element behind the scenes and offers similar functionality and API for its users. labelproperty is always required for an accessible input control.labelHiddenproperty hides the above label visually, but still keeps it accessible for assistive technologies.- Use
roleproperty to define a specific role attribute for the input. - When you need to disable an input, use
disabledproperty as it conveys this information correctly to assistive technologies as well. - Use the provided
typeproperty to adapt the input for different tasks. Depending on the device and browser this setting also switches the visible on-screen keyboard for users of supported devices. accessibleActiveDescendantproperty can be used to indicate the id of a related component’s visually focused element.accessibleAutocompleteproperty can be used to indicate in what mode user input completion suggestions are provided. Note that is is different fromautoComplete.accessibleAutocompletecontrolls whether completions are"inline"(like with searchbox) or"list"(like with combobox).accessibleControlsproperty can be used to add an aria-controls attribute. Use the attribute to indicate the id of a component controlled by this component.accessibleOwnsproperty can be used to indicate the id of a component owned by the input.accessibleDescribedByproperty can be used to indicate the id of a component which contains descriptive/help text related to the input.
Additional considerations #
- Don’t use the
placeholderproperty to provide information that is required to use the input. - Use
autoCompleteto help browser offer right type of completion suggestions. This improves both usability and accessibility, as the user needs to type less.
Integration
For integration, event and theming guidelines, please see Using Components. This documentation explains how to implement and use Duet’s components across different technologies like Angular, React or Vanilla JavaScript.
Tutorials
Follow these practical tutorials to learn how to build simple page layouts using Duet’s CSS Framework, Web Components and other features:
Building Layouts
TutorialsUsing CLI Tools
TutorialsCreating Custom Patterns
TutorialsServer Side Rendering
TutorialsSharing Prototypes
TutorialsUsage With Markdown
Troubleshooting
If you experience any issues while using a component, please head over to the Support page for more guidelines and help.