GrInput
A single-line field for text, search and short values.
Overview
When to take it
- a string is entered — a name, an address, a search query, a password: the basic control of a form;
- the field has addons — a unit of measurement, the prefix of a protocol, a search icon through the slots;
- the length is limited —
maxlengthwith a character counter instead of silent truncation; - background work is going on in the field — a busy indicator that does not block typing.
When to take something else
| Need | Take |
|---|---|
| Text across several lines | GrTextarea |
| A number with steps is entered | GrNumberInput |
| There are several values, as chips | GrInputTag |
| A choice among ready options | GrSelect |
| A search with suggestions | GrAutocomplete |
| A date or a time | GrDatePicker |
Variants every tone and view, rendered by the real component
state \ shape | box | pill |
|---|---|---|
default | ||
success | ||
warning | ||
danger |
Sizes
xssmmdlg
States
normaldisabledloadingreadonlyinvalid
The grid is live: hover and press it. Hover, focus and active states are not drawn here — they belong to the browser, not to a prop.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrInput
v-model="value"
placeholder="[email protected]"
type="email"
/>Examples 3 each with a live demo and its code
A search in the toolbar
shape="pill"sets a toolbar search apart from the fields of a form,clearablegives a one-click way back to the full list, and the glyph sits in aninlineaddon instead of a boxed segment. The list filters as you type and keeps its height, so the page does not jump.Members · 5A product editor with addons
Addons hold what is not the value — the shop address, the currency, the unit — and
describe-addonslets a screen reader hear them where they carry meaning.suffix-fixedwith onesuffix-min-widthkeeps EUR and kg the same width, so the numbers form a column, andtext-align="right"lines up their digits.Linen shirt, sand shop.example.com/p/EUREURkgA username checked as you type
loadingshows the availability check inside the field and setsaria-busy;statecolours the answer and adds its icon.maxlengthwithshow-countkeeps the limit in sight, andpassword-toggleadds the eye with a proper accessible name.Create your account Letters, digits and hyphens.
4 / 2012 characters or more
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
disabled | boolean | undefined | false | Switch the field off: the clear button and the password reveal disappear. Combined with `GrFormField`. |
type | "number" | "search" | "text" | "email" | "password" | "tel" | "url" | undefined | "text" | The native `type`: it changes the keyboard and the browser's format check. `text` by default. |
modelValuev-model | string | undefined | "" | The value of the field. Optional: without a `v-model` the field is drawn empty. The default is an empty string rather than `undefined`: the length of the value is read directly (`showClear`), and an `undefined` would crash the render. |
readonly | boolean | undefined | false | Read-only: the value is visible and selectable, but is not edited. |
invalid | boolean | undefined | false | A validation error: a danger border and `aria-invalid`. Combined with the error of `GrFormField`. |
required | boolean | undefined | false | A required field (`aria-required`). It adds to the `required` of `GrFormField`. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The height and the type size by the controls scale. `md` by default; taken from `GrConfigProvider`. |
placeholder | string | undefined | undefined | A hint in an empty field. It does not replace the label: the name is given by `GrFormField` or `ariaLabel`. |
ariaLabel | string | undefined | undefined | The accessible name outside `GrFormField`. |
clearable | boolean | undefined | undefined | Show the clear button when there is a value (and it is not disabled/readonly). |
loading | boolean | undefined | false | Background work on the field (checking whether a login is taken, autosaving): a spinner in the trailing area plus `aria-busy`. Typing is not blocked — for that there are `disabled`/`readonly`. |
clearLabel | string | undefined | undefined | The i18n aria-label of the clear button. |
name | string | undefined | undefined | The native `name` — the name of the field when the form is submitted. |
prefixMinWidth | string | undefined | undefined | The minimum width of the prefix. By default — by the size step; for an `inline` addon — `0`. |
prefixMaxWidth | string | undefined | undefined | The maximum width of the prefix: wider content is clipped. |
suffixMinWidth | string | undefined | undefined | The minimum width of the suffix. By default — by the size step; for an `inline` addon — `0`. |
suffixMaxWidth | string | undefined | undefined | The maximum width of the suffix: wider content is clipped. |
prefixFixed | boolean | undefined | false | A fixed width for the prefix/suffix: the addon gets a rigid width (from `*MaxWidth` → `*MinWidth` → the default), and the content is clipped at the edge (the prefix on the right, the suffix on the left). By default the addons "stretch" to the content (within min/max), and the excess is clipped by the shell. |
suffixFixed | boolean | undefined | false | A fixed width of the suffix — as with `prefixFixed`, only the excess is clipped on the left. |
shape | "box" | "pill" | undefined | undefined | The shape of the border. `box` — the rounding of the controls scale, as it has always been; `pill` — a pill track, as in `GrSegmented`. The shape pulls the horizontal padding along: in a pill it is no less than half the height, otherwise the text runs into the arc. |
id | string | undefined | undefined | The `id` of the field. Unset — taken from `GrFormField`, so that the label finds the field. |
state | "default" | "success" | "warning" | "danger" | undefined | "default" | Border highlighting at the developer's discretion. `success` and `warning` add an icon and a hidden label; `invalid` is stronger. `default` by default. |
autocomplete | string | undefined | undefined | The native `autocomplete`: what the browser fills the field with — `email`, `one-time-code` and so on. |
inputmode |
| undefined | The native `inputmode`: which on-screen keyboard to show without changing `type`. |
maxlength | number | undefined | undefined | A limit on the length plus the basis for the character counter. |
showCount | boolean | undefined | false | Show the character counter (`len` or `len/maxlength`). |
describeAddons | boolean | undefined | false | The `prefix`/`suffix` addons carry meaning rather than decorate. By default they are hidden from the screen reader: most often there is a search icon or a glyph repeating the label of the field, and a second reading gets in the way. But `https://` or `₽` are sometimes the only place that says what to enter — then the addon goes into the `aria-describedby` of the field. Mark a decorative icon inside the slot `aria-hidden` yourself: the prop lifts the hiding from the whole wrapper rather than picking its content apart. |
passwordToggle | boolean | undefined | false | A show/hide password button (only with `type="password"`). |
passwordShowLabel | string | undefined | undefined | The i18n aria-label of the show/hide password button. |
passwordHideLabel | string | undefined | undefined | The i18n aria-label of the button that hides a revealed password. Unset — from the locale. |
textAlign | GrInputTextAlign | undefined | "left" | The alignment of the text in the field. `left` by default. |
addon | "inline" | "segment" | undefined | "segment" | How the `#prefix`/`#suffix` addons look. `segment` (the default) is a compartment of its own, cut off by a border and aligned to the size step: that way a field with "₽" and a field with "USD" stand in a column. `inline` is an ornament inside the border: no separator, no width of its own. The difference is not cosmetic. A search bar with a magnifier in a segment reads as a composite element — a field with a button glued to it — rather than as one field; that is exactly why an icon inside the border could not be expressed with an addon, and consumers gave it up entirely. |
Slots
| Slot | Type | Description |
|---|---|---|
prefix | any | An addon to the left of the field: an icon, a currency code, a label. |
suffix | any | An addon to the right of the field: a unit of measurement, a hint. |
Events
| Event | Signature | Description |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: string): void | — |
change | (event: "change", value: string): void | — |
clear | (event: "clear"): void | — |
focus | (event: "focus", event: FocusEvent): void | — |
blur | (event: "blur", event: FocusEvent): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
focus | () => void | Put the focus into the field. |
blur | () => void | Remove the focus from the field. |
select | () => void | Select the content of the field — so that a prefilled value is overwritten by typing. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrInputConfigurableProps, GrInputProps, GrInputEmits } from '@feugene/granularity/components/GrInput'Filedist/types/components/GrInput/config.d.ts
const grInputConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrInput">;Filedist/types/components/GrInput/defaults.d.ts
interface GrInputConfigurableProps {
size: GrComponentSize;
clearable: boolean;
shape: GrControlShape;
}Filedist/types/components/GrInput/GrInput.vue.d.ts
interface GrInputProps {
modelValue?: string;
type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url';
placeholder?: string;
autocomplete?: string;
inputmode?: InputHTMLAttributes['inputmode'];
disabled?: boolean;
readonly?: boolean;
invalid?: boolean;
required?: boolean;
ariaLabel?: string;
state?: 'default' | 'success' | 'warning' | 'danger';
name?: string;
id?: string;
size?: GrInputSize;
shape?: GrControlShape;
clearable?: boolean;
clearLabel?: string;
maxlength?: number;
showCount?: boolean;
describeAddons?: boolean;
loading?: boolean;
passwordToggle?: boolean;
passwordShowLabel?: string;
passwordHideLabel?: string;
textAlign?: GrInputTextAlign;
prefixMinWidth?: string;
prefixMaxWidth?: string;
suffixMinWidth?: string;
suffixMaxWidth?: string;
prefixFixed?: boolean;
suffixFixed?: boolean;
addon?: 'segment' | 'inline';
}interface GrInputEmits {
(e: 'update:modelValue', value: string): void;
(e: 'change', value: string): void;
(e: 'focus', event: FocusEvent): void;
(e: 'blur', event: FocusEvent): void;
(e: 'clear'): void;
}type GrInputSize = GrComponentSize;type GrInputTextAlign = 'left' | 'center' | 'right';Filedist/types/components/GrInput/safelist.d.ts
const grInputSafelist: string[];Filedist/types/components/GrInput/index.d.ts
type GrInputInstance = ComponentExposed<typeof GrInputComponent>;Accessibility
APG pattern
—
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
The trailing controls are reachable from the keyboard
The clear and show-password buttons stand in the ordinary tab order: Tab from the field
leads to the cross, then to the eye. They used to carry tabindex="-1" — the widget was
declared accessible (aria-label, aria-pressed), but there was no using it without a
mouse.
Both buttons return the focus to the field after being pressed, so clearing does not drop the focus into nowhere: the button disappears together with the emptied value.
Addons: a segment or an ornament
#prefix and #suffix are drawn in two forms, and those are different entities rather than
the styling of one.
segment (the default) is a compartment of its own, cut off by a border and aligned to the
size step: a field with ”₽” and a field with “USD” stand in a column instead of dancing by
width. That is what is expected of a money field and of a field with a unit.
addon="inline" is an ornament inside the border: no separator, no width of its own. The
magnifier of a search bar, a currency symbol, a counter. In a segment the magnifier would
read as a field with a button glued to it, so an icon inside the border could not be
expressed with an addon at all — people simply gave it up.
<GrInput v-model="query" addon="inline">
<template #prefix>
<GrIcon size="sm"><span class="i-lucide-search" /></GrIcon>
</template>
</GrInput>The padding of the field is computed from the measured width of the addon in both modes: the
text starts right after the ornament rather than in an empty compartment.
prefixFixed/suffixFixed and *MinWidth/*MaxWidth work as before — they are about the
segment.
A meaningful addon is announced
Addons are hidden from the screen reader by default: most often there is a search magnifier or a glyph repeating the label of the field there, and a second reading gets in the way.
But https:// in an address field or ₽ beside a label without a currency can be the only
place where it is said what to enter. For them there is describeAddons.
It does not merely lift the hiding: a <div> beside the field does not enter its accessible
name, and a screen reader would stumble on the text only while walking the page — not when it
is needed. That is why the addon goes into the aria-describedby of the field itself.
<GrInput v-model="site" describe-addons>
<template #prefix>
https://
</template>
</GrInput>Mark a decorative icon inside the slot aria-hidden yourself: the prop lifts the hiding from
the whole wrapper rather than taking its content apart.
The shape of the frame
shape="pill" gives the same rounding as GrSegmented; the default is box, the rounding of
the control scale.
The shape brings the horizontal padding with it: on a pill it is no less than half the height,
otherwise the text runs into the arc. The addons follow it too — the compartment is clipped
along the outer contour by the shell (overflow-hidden), while its separating border stays
straight.
The axis and its limits — ../sizes.md; it also says which fields of a form did
not get a shape and why.
The character counter
showCount draws 12 / 60 (or just the length, if maxlength is not set) and links the
counter to the field through aria-describedby — on focus a screen reader reads the
remainder together with the label.
Exhausting the limit is additionally announced by a role="status" live region
(gr.input.limitReached). The region stays silent until the limit is reached: reading every
character aloud is a guaranteed way to make the field unusable for a screen reader.
<GrInput v-model="bio" :maxlength="60" show-count clearable />The events
| Event | When |
|---|---|
update:modelValue | on every keystroke |
change | the value is committed: the native change (on blur or Enter) or the clear button |
focus / blur | the focus arrived/left, the argument is a FocusEvent |
clear | the value was erased with the clear button |
clear exists separately from update:modelValue because by the value alone a programmatic
clearing cannot be told from a manual erasure — and the reaction of a form to them is usually
different.
Clearing with the button sends all three events in a row: update:modelValue, change,
clear. It is as much a commitment of the value as the departure of the focus — a
subscription for “the value has been set” would miss exactly it. The native equivalent
behaves the same way: the cross of an <input type="search"> sends both input and
change.
The imperative API
<GrInput ref="field" v-model="value" />field.value gives away focus(), blur() and select(). The last one is the companion of
focus() for the “the value has been filled in, let it be overwritten” scenario.
loading
A spinner in the trailing area plus aria-busy on the field: checking whether a login is
taken, autosaving, loading a reference list. Typing is not blocked in the process — for a
prohibition there are disabled and readonly. The spinner reserves room on the right on a
par with the buttons, so the text does not move under it.
The states and the tokens
state (success / warning / danger) colours the border and the focus ring; invalid
(its own prop or an error from GrFormField) always leads to the danger look.
success and warning additionally show an icon and a hidden label that goes into the
aria-describedby of the field. A border colour as the only carrier of meaning is a WCAG 1.4.1
matter: the icon adds a shape, the label adds a programmatic channel, which these two states
lack (there is no ARIA equivalent of aria-invalid for them). danger gets no icon: its meaning
is already carried by aria-invalid and the error text from GrFormField.
The sign goes out when the field is declared invalid: a “correct” tick beside
aria-invalid="true" would contradict it.
A blocked field is dimmed with the --gr-muted background and --gr-muted-fg text rather
than with transparency: opacity dilutes text tokens tuned to AA and lowers the contrast.
The classes of the sizes, the alignment and the states live in grInputStyles.ts and are
declared in the safelist as a whole — together with the horizontal padding, which the same
size also needs as a number (the addons set the padding with an inline style, and that
overrides a class).
type
text, email, password, number, search, tel, url. The last two change the
on-screen keyboard on mobile and switch on the browser’s validation of the format.
Props in this example