Input
YueInput is a native single-line <input>: v-model, sizes, states, prefix/suffix content, and a real clear button. It does not own the label, helper text or error copy—that is YueField's job.
Every preview box has its own appearance toolbar in the top-right corner (light / dark / Accent / density / reset). It changes tokens, and every example on the page follows along.
Basic input and v-model
The value is always a string. The base input does not guess whether '42' is a number; conversion is the consumer's job, or a future typed field's.
The value is:
Sizes
The three sizes reuse Button's control-size contract (ComponentSize) rather than the input inventing its own pixel values. When size is omitted it falls back to the app-level configuration.
size | Height | Inline padding | Font size | Prefix/suffix icon |
|---|---|---|---|---|
sm | --input-height-sm | --input-padding-inline-sm | --input-font-size-sm | --input-icon-size-sm |
md | --input-height-md | --input-padding-inline-md | --input-font-size-md | --input-icon-size-md |
lg | --input-height-lg | --input-padding-inline-lg | --input-font-size-lg | --input-icon-size-lg |
disabled / readonly / invalid
These three are three different things, and they must look different too:
| State | Native behaviour | Visual semantics |
|---|---|---|
disabled | Native disabled: not focusable, not submitted, screen readers do not treat it as editable | Disabled background + disabled text + not-allowed cursor |
readonly | Native readonly: still focusable, still selectable and copyable, still submitted | Quieter background, text stays readable |
invalid | aria-invalid="true" | Error boundary |
The difference between readonly and disabled is not a matter of style
A readonly field exists for values that are "visible, selectable and copyable, but not editable"—an order number, say. Expressing that with disabled makes the value unselectable and uncopyable, and it is not submitted with the form either. Their contrast gates differ too: the disabled state is exempt under WCAG, the readonly state is not, so readonly text is still audited against 4.5:1.
invalid only says "validation failed", it does not explain why
It sets aria-invalid="true" and switches the error boundary, but it renders no error copy at all. Where the error is and how to announce it is YueField's business—if both components wrote their own error copy, there would be two sources of truth to keep in sync.
For focus priority in the invalid state, see the guide.
placeholder and native attributes
name, autocomplete, inputmode, required, maxlength, minlength, pattern, aria-* and id are all native attributes passed through, not Yue Props. They land on the real <input> inside, so label for, form submission, browser validation and autofill keep working as usual.
We use it to send the sign-in link and never make it public.
The table below shows which element each attribute ends up on—this is the component's public DOM contract:
| Attribute | Lands on | Why |
|---|---|---|
id, name, autocomplete, inputmode, required, maxlength, minlength, pattern, aria-* | The inner <input> | They are form-control semantics; putting them on a <div> is as good as not writing them |
class, style, data-* | The outer .yue-input | They are the identity of "the whole component" and its test hooks |
prefix / suffix
Slots, bound to no icon library. Mark decorative icons aria-hidden="true" yourself; do not put interactive controls inside a decorative span—when you need a button, use clearable or build your own separate control.
Slots only add decoration: they do not change the value, the selection or keyboard behaviour, and they do not create extra focus targets.
clearable
The clear control is a real <button type="button">: it is reachable by Tab, it has an accessible name, and it has its own hover and focus-visible styles. After clearing, focus stays inside the input—clearing is the start of "typing again", not the end of editing.
The clear control does not appear when the field is disabled, readonly or empty: offering "clear" on an input you cannot change is a lie.
Accessible name
The clear button's aria-label comes from Yue's locale catalog (key input.clear), English Clear by default. It is not a component Prop—one string per Prop serves one component only, has to be repeated at every call site, and turns "translating" into "editing markup".
The three fields above have exactly the same markup; the only difference is which locale the outer LocaleScope switches the subtree to. The component does not know that "language" exists, it only reads t('input.clear'); verify:visual asserts three aria-label values in a real browser: the default reads the page language, the second equals '清空', and the third also equals '清空' — but that third one is the fallback chain at work: there is no zh-Hans-CN pack in the repository, so zh-Hans-CN falls back through zh-Hans-CN → zh-CN → zh → en-US to the Chinese pack instead of failing or rendering the key.
provideLocale() is exported from @yue-ui/vue/locale; the full contract is in the i18n guide.
Password
The first version only requires type="password" to pass through correctly. The plaintext toggle is the consumer's own suffix control—the base input does not secretly inject icons and internal state.
Light / dark / Accent
All input colours come from tokens, so switching theme needs no JavaScript: --input-background, --input-border-color*, --input-color and --input-placeholder-color each resolve to different values in light and dark.
In dark mode the Error boundary switches to a lighter red on a deeper ramp so it stays visible—these values are all inside the 110 gates of pnpm audit:tokens (including readonly text, focus boundary, error boundary, prefix/suffix text and the clear control). The assertions are not only at the token layer: verify:visual measures the real rendered result in the browser under all three environments—light, dark and a neutral Accent.
The component does not know the theme
There is not a single line of JS in YueInput that checks whether the current theme is light or dark, and there is no theme Prop. The theme is the result of token resolution, not component state—so server-side rendering does not produce different HTML for different themes.
State matrix
Keyboard focus does not depend on colour
:focus-visible draws a focus ring around the whole field (--input-focus-ring-*) rather than just recolouring the border. When the invalid state is combined with focus, the error boundary keeps the error colour and the focus cue is carried by the ring—so keyboard users do not lose the "something is wrong here" information just because they focused the field.
Narrow screens
The field is 100% wide and allowed to shrink inside a flex / grid column (min-width: 0), so narrow screens only constrain it through the container and never push the page into horizontal scrolling.
RTL
dir belongs to the field itself rather than to the control inside it: the outer element is what lays out the prefix/suffix content. So dir lands on .yue-input and the inner <input> inherits it—the whole field mirrors together, instead of only the typed text mirroring while the prefix/suffix icons stay in their original order.
Putting dir on an ancestor container works the same way: the inner <input> inherits the direction and the outer element remains the layout owner.
When not to use it
- You need a label, helper text, error copy and a required marker → wait for
YueField, do not push form layout back into the input; - You need multiple lines →
YueTextarea, whose line height and resize differ from the single-line input; - You need number stepping, dates, dropdowns or tag input → separate components, rather than one
typeparameter covering everything; - You need number formatting → build a separate formatter first, and only then consider wiring it to an input.
Which "non-editable" state to choose
These three states are often mixed up, but their native behaviour is completely different:
| The question you are asking | Use |
|---|---|
| The user entered something wrong and needs to retype it | invalid |
| The value comes from the system and the user can only view and copy it | readonly |
| This field simply does not apply right now | disabled |