Text fields

Text fields let people enter and edit text. They come in two styles, filled and outlined. Both support a label, supporting text and validation.

Usage

Text fields work like <input> elements with a label.

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Label" value="Value" />
<TextField label="Label" value="Value" variant="filled" />

Input type

The type attribute changes how the field works, for example which keyboard shows or which validation applies by default.

  • type="text" (default)
  • type="email"
  • type="password"
  • type="url"
  • type="number"
  • type="search"
  • type="tel"
  • type="date"
  • type="time"
  • type="datetime-local"
  • type="datetime"
  • type="textarea"
<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Email" type="email" />
<TextField label="Password" type="password" />

Disabled

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Label" disabled value="Value" />
<TextField label="Label" disabled value="Value" variant="filled" />

Labels

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Country" />
<TextField placeholder="email@domain.com" />
<label id="city-label">
	City
	<TextField aria-labelledby="city-label" />
</label>
Nesting text fields in labels without aria-labelledby is not supported. To avoid an id, use aria-label instead.

Textarea

A type="textarea" field grows with its content, from minLines up to maxLines, then scrolls.

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Message" type="textarea" />
<TextField label="Message" type="textarea" variant="filled" />

Use minLines and maxLines to change that range.

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Bio" type="textarea" minLines={3} maxLines={6} />

Chat prompt

A common use of an auto-growing textarea is a chat prompt, like the input of an AI tool. The "+" button at the start opens a menu.

<script lang="ts">
	import { IconButton, Menu, MenuItem, TextField } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	let chatPromptPlusBtn: HTMLElement | undefined = $state()
</script>

<TextField
	aria-label="Prompt"
	placeholder="Ask anything"
	type="textarea"
	maxLines={8}
	style="width:22rem"
>
	{#snippet start()}
		<IconButton
			title="Add"
			style="anchor-name:--chat-prompt-plus"
			command="toggle-popover"
			commandfor="chat-prompt-menu"
			bind:element={chatPromptPlusBtn}
		>
			<Icon>add</Icon>
		</IconButton>
	{/snippet}
	{#snippet end()}
		<IconButton title="Send"><Icon>send</Icon></IconButton>
	{/snippet}
</TextField>
<Menu anchor={chatPromptPlusBtn} id="chat-prompt-menu" style="position-anchor:--chat-prompt-plus">
	<MenuItem>
		Web search
		{#snippet start()}<Icon>language</Icon>{/snippet}
	</MenuItem>
	<MenuItem>
		Connect apps
		{#snippet start()}<Icon>webhook</Icon>{/snippet}
	</MenuItem>
</Menu>

Icons

<script lang="ts">
	import { IconButton, TextField } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<TextField placeholder="Search" type="search" inputmode="search">
	{#snippet start()}<Icon>search</Icon>{/snippet}
</TextField>
<TextField label="Password" type="password">
	{#snippet end()}
		<IconButton toggle title="Toggle visibility">
			{#snippet selectedIcon()}
				<Icon>visibility_off</Icon>
			{/snippet}
			<Icon>visibility</Icon>
		</IconButton>
	{/snippet}
</TextField>
<TextField label="Username" aria-invalid issues={[{ message: 'Username not available' }]}>
	{#snippet end()}
		<Icon>error</Icon>
	{/snippet}
</TextField>

Prefix and suffix

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField
	label="Amount"
	value="0"
	inputmode="numeric"
	prefixText="$"
	prefixLabel="US dollars"
	suffixText=".00"
	type="number"
/>

Supporting text

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Username" supportingText="Your username is your unique identifier." />
<TextField
	label="Email"
	type="email"
	inputmode="email"
	required
	supportingText="Email is required"
/>

Character counter

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField label="Name" maxlength={10} />

Validation

Constraint validation

<script lang="ts">
	import { Button, TextField } from 'noph-ui'
</script>

<form>
	<div style="display:flex;gap:1rem;flex-wrap: wrap;align-items: baseline;">
		<TextField label="First name" required />
		<TextField label="Last name" required pattern="[a-zA-Z]+" supportingText="Characters only" />
	</div>
	<div class="button-footer">
		<Button variant="filled">Submit</Button>
	</div>
</form>

<style>
	.button-footer {
		display: flex;
		justify-content: flex-end;
		margin-top: 1rem;
	}
</style>

Manual validation

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField
	label="Username"
	aria-invalid
	issues={[{ message: 'Username not available' }]}
	value="eric20"
/>

Theming

Filled text field tokens

TokenDefault value
--np-filled-text-field-container-shape--np-shape-corner-extra-small
--np-filled-text-field-container-color--np-color-surface-container-highest
--np-filled-text-field-label-text-color--np-color-primary
--np-filled-text-field-focus-active-indicator-color--np-color-primary

Filled text field example

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField
	variant="filled"
	label="Filled"
	--np-filled-text-field-container-shape="0"
	--np-filled-text-field-container-color="var(--np-color-surface-container)"
	--np-filled-text-field-label-text-color="var(--np-color-tertiary)"
	--np-filled-text-field-focus-active-indicator-color="var(--np-color-tertiary)"
/>

Outlined text field tokens

TokenDefault value
--np-outlined-text-field-container-shape--np-shape-corner-extra-small
--np-outlined-text-field-label-text-color--np-color-primary
--np-outlined-text-field-focus-outline-color--np-color-primary

Shared tokens

TokenDefault value
--np-picker-indicator-displaynone

A date, time or datetime-local field hides the browser's picker button. Set --np-picker-indicator-display to block to show it.

Outlined text field example

<script lang="ts">
	import { TextField } from 'noph-ui'
</script>

<TextField
	label="Outlined"
	--np-outlined-text-field-container-shape="0"
	--np-outlined-text-field-label-text-color="var(--np-color-tertiary)"
	--np-outlined-text-field-focus-outline-color="var(--np-color-tertiary)"
/>

Accessibility

The field is wrapped in a <label> that points at the input with for, so a click anywhere on the field puts the caret in it. Buttons in the start and end slots keep their own names. The input is named only by the label text, through aria-labelledby. Underneath is a native <input> or <textarea>, which handles type, required, the keyboard and autofill.

Supporting text is linked to the field with aria-describedby. An error, from errorText or issues, switches that to aria-errormessage and announces the message with role="alert". A required field shows an asterisk. Use noAsterisk to remove it when your form marks required fields another way.

Prefix, suffix and the character counter are also linked with aria-describedby. Use prefixLabel and suffixLabel to give symbols a spoken name, for example "Euro" for "€". The counter is read as "Character count, 5/20". counterLabel translates the first part.

API

Attributes

AttributeTypeDefaultDescription
type'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'datetime' | 'textarea''text'Specifies the type of the field.
minLinesnumber1Minimum number of visible lines. Only applies to type="textarea".
maxLinesnumber4Number of lines the field grows to before it scrolls. Only applies to type="textarea".
variant'outlined' | 'filled''outlined'Visual appearance
labelstring | undefinedundefinedLabel of the text field.
supportingTextstring''Provides additional information below the text field, such as usage guidelines.
startSnippet | undefinedundefinedIcon displayed at the beginning of the text field.
endSnippet | undefinedundefinedIcon displayed at the end of the text field.
disabledbooleanfalseDisables the text field.
noAsteriskbooleanfalseHides the asterisk on the floating label of a required text field.
issues{ message: string }[]undefinedError messages shown instead of the supporting text. Pass a remote form field's issues().
prefixTextstring''An optional prefix to display before the input value.
suffixTextstring''An optional suffix to display after the input value.
prefixLabelstringundefinedSpoken in place of prefixText, for example "Euro" for "€".
suffixLabelstringundefinedSpoken in place of suffixText, for example "at gmail dot com".
counterLabelstring'Character count'Spoken before the character counter that maxlength shows.
defaultValuestring | number | null | undefinedundefinedValue the field starts with and that a form reset returns it to, without taking control of value.
populatedbooleanfalseKeeps the label in its floating position even while the input is empty. Set it when something other than the value fills the field, such as chips.

Bindables

AttributeTypeDescription
valuestring | number | null | undefinedValue of the input or textarea.
focusedbooleanWhether the input currently has focus. Defaults to false.
elementHTMLSpanElementThe component's root DOM element, bound with bind:this.
inputElementHTMLInputElement | HTMLTextAreaElement | undefinedThe input or textarea element.
clientWidth, clientHeightnumber | undefinedSize of the field, for positioning other elements next to it. AutoComplete uses them to size its menu.