Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

    Components

    Text field

    Full supportSupported since v125.Full supportSupported since v152.Full supportSupported since v26.2.

    Anatomy

    • <TextField>

      Container element.

    • v-slot:label

      The label for the field.

    • v-slot:description

      Description text displayed above the field.

    • .ui-field

      The boxed input area.

    • v-slot:header

      Content above the input, inside the border, with a divider.

    • v-slot:prefix

      Content at the inline-start of the field, inside the border.

    • v-model

      The input element.

    • v-slot:suffix

      Content at the inline-end of the field, inside the border.

    • v-slot:footer

      Content below the input, inside the border, with a divider.

    • v-slot:end-text

      Supporting text displayed below the field.

    Variants

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Outlined" placeholder="Placeholder" />
    <TextField label="Filled" placeholder="Placeholder" filled />
    </template>
    <!--[--><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Outlined<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled"
    ><span class="ui-label"><!--[-->Filled<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><!--]-->

    Sizes

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Small outlined" placeholder="Placeholder" small />
    <TextField label="Small filled" placeholder="Placeholder" small filled />
    </template>
    <!--[--><label class="ui-text-field ui-small"
    ><span class="ui-label"><!--[-->Small outlined<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled ui-small"
    ><span class="ui-label"><!--[-->Small filled<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><!--]-->

    End text

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Label" placeholder="Outlined" endText="Supporting text" />
    </template>
    <label class="ui-text-field"
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Outlined"
    /><!----><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->Supporting text<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    >

    Affix

    Use the prefix, suffix, header, and footer slots to affix content inside the field's border. Prefix and suffix sit beside the input, while header and footer span the field's full width with a divider.

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Amount" placeholder="0.00">
    <template #prefix>¢</template>
    <template #suffix>EUR</template>
    </TextField>
    <TextField label="Website" placeholder="example.com">
    <template #prefix>https://</template>
    </TextField>
    <TextField label="Weight" type="numeric" placeholder="0">
    <template #suffix>kg</template>
    </TextField>
    <TextField label="Search" placeholder="Search...">
    <template #prefix
    ><svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <circle cx="11" cy="11" r="8"></circle>
    <path d="m21 21-4.3-4.3"></path></svg
    ></template>
    </TextField>
    </template>
    <!--[--><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Amount<!--]--></span><!----><span
    class="ui-field"
    ><input type="text" placeholder="0.00" /><span class="ui-prefix"
    ><!--[-->¢<!--]--></span
    ><span class="ui-suffix"><!--[-->EUR<!--]--></span><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Website<!--]--></span><!----><span
    class="ui-field"
    ><input type="text" placeholder="example.com" /><span class="ui-prefix"
    ><!--[-->https://<!--]--></span
    ><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Weight<!--]--></span><!----><span
    class="ui-field"
    ><input
    inputmode="numeric"
    pattern="[0-9]*"
    type="text"
    placeholder="0"
    /><!----><span class="ui-suffix"><!--[-->kg<!--]--></span
    ><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Search<!--]--></span><!----><span
    class="ui-field"
    ><input type="text" placeholder="Search..." /><span class="ui-prefix"
    ><!--[--><svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <circle cx="11" cy="11" r="8"></circle>
    <path d="m21 21-4.3-4.3"></path></svg
    ><!--]--></span
    ><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><!--]-->

    Headers and footers

    Use the header slot for inside-field captions (filenames, categories) and the footer slot for counters, hints, or action buttons.

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Username" placeholder="Enter your name">
    <template #header>Full Name</template>
    </TextField>
    <TextField label="Tagline" placeholder="A short description">
    <template #footer>0 / 80</template>
    </TextField>
    </template>
    <!--[--><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Username<!--]--></span><!----><span
    class="ui-field"
    ><input type="text" placeholder="Enter your name" /><!----><!----><span
    class="ui-header"
    ><!--[-->Full Name<!--]--></span
    ><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Tagline<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="A short description"
    /><!----><!----><!----><span class="ui-footer"
    ><!--[-->0 / 80<!--]--></span
    ></span
    ><!----><!--[--><!--]--></label
    ><!--]-->

    Validation

    Add the required attribute on the component. It is forwarded to the underlying <input>.

    Use the error prop to toggle invalid styles. It rendersdata-invalid on the root element. Make use of the end text to give extra feedback on the error.

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <div class="example-row">
    <TextField label="I'm required" placeholder="Placeholder" required />
    <TextField label="So am I!" placeholder="Placeholder" required filled />
    </div>
    <div class="example-row">
    <TextField
    label="Label"
    placeholder="Placeholder"
    value="This isn't right"
    endText="Only double-negatives are allowed."
    error
    />
    <TextField
    label="Label"
    placeholder="Placeholder"
    value="Uh-oh"
    endText="Only letters from the first half of the alphabet are allowed."
    error
    filled
    />
    </div>
    </template>
    <!--[-->
    <div class="example-row">
    <label class="ui-text-field"
    ><span class="ui-label"><!--[-->I'm required<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    required
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled"
    ><span class="ui-label"><!--[-->So am I!<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    required
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    >
    </div>
    <div class="example-row">
    <label class="ui-text-field" data-invalid
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    value="This isn't right"
    /><!----><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->Only double-negatives are allowed.<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled" data-invalid
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Placeholder"
    value="Uh-oh"
    /><!----><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->Only letters from the first half of the alphabet are allowed.<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    >
    </div>
    <!--]-->

    Spread

    Use the spread boolean prop to display the label and description on the left with the input on the right. The layout collapses to a column on narrow containers.

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField spread placeholder="Evil Rabbit">
    <template #label>Name</template>
    <template #description>Provide your full name for identification</template>
    </TextField>
    <TextField spread placeholder="you@example.com" type="email" filled>
    <template #label>Email</template>
    <template #description>We'll use this to contact you</template>
    <template #end-text>Please use a valid email address</template>
    </TextField>
    <TextField spread required label="Required">
    <template #description>You must fill this in</template>
    </TextField>
    <TextField spread disabled label="Disabled">
    <template #description>This field is disabled</template>
    </TextField>
    <TextField spread error label="Invalid Name">
    <template #description>This field has an error</template>
    <template #end-text>This value is too short.</template>
    </TextField>
    <TextField spread label="Amount" placeholder="0.00">
    <template #description>Daily spending limit</template>
    <template #prefix>¢</template>
    <template #suffix>EUR</template>
    </TextField>
    <TextField spread label="Website" placeholder="example.com" filled>
    <template #description>Your public profile URL</template>
    <template #prefix>https://</template>
    <template #end-text>Must include a valid domain</template>
    </TextField>
    <TextField spread label="Project name" placeholder="my-project">
    <template #description>Used to generate the project URL</template>
    <template #header>acme.dev/</template>
    <template #footer>Lowercase letters and dashes only</template>
    </TextField>
    <TextField
    spread
    filled
    label="API key"
    placeholder="Paste your key"
    type="password"
    >
    <template #description>Stored encrypted at rest</template>
    <template #prefix>
    <svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
    <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
    </svg>
    </template>
    <template #header>Secret</template>
    <template #footer>Rotates every 90 days</template>
    <template #end-text>Treat like a password</template>
    </TextField>
    </template>
    <!--[--><label class="ui-text-field ui-spread"
    ><span class="ui-label"><!--[-->Name<!--]--></span
    ><span class="ui-start-text"
    ><!--[-->Provide your full name for identification<!--]--></span
    ><span class="ui-field"
    ><input
    type="text"
    placeholder="Evil Rabbit"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled ui-spread"
    ><span class="ui-label"><!--[-->Email<!--]--></span
    ><span class="ui-start-text"
    ><!--[-->We'll use this to contact you<!--]--></span
    ><span class="ui-field"
    ><input
    type="email"
    placeholder="you@example.com"
    /><!----><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->Please use a valid email address<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    ><label class="ui-text-field ui-spread"
    ><span class="ui-label"><!--[-->Required<!--]--></span
    ><span class="ui-start-text"><!--[-->You must fill this in<!--]--></span
    ><span class="ui-field"
    ><input type="text" required /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-spread"
    ><span class="ui-label"><!--[-->Disabled<!--]--></span
    ><span class="ui-start-text"><!--[-->This field is disabled<!--]--></span
    ><span class="ui-field"
    ><input type="text" disabled /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-spread" data-invalid
    ><span class="ui-label"><!--[-->Invalid Name<!--]--></span
    ><span class="ui-start-text"><!--[-->This field has an error<!--]--></span
    ><span class="ui-field"
    ><input type="text" /><!----><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->This value is too short.<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    ><label class="ui-text-field ui-spread"
    ><span class="ui-label"><!--[-->Amount<!--]--></span
    ><span class="ui-start-text"><!--[-->Daily spending limit<!--]--></span
    ><span class="ui-field"
    ><input type="text" placeholder="0.00" /><span class="ui-prefix"
    ><!--[-->¢<!--]--></span
    ><span class="ui-suffix"><!--[-->EUR<!--]--></span><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled ui-spread"
    ><span class="ui-label"><!--[-->Website<!--]--></span
    ><span class="ui-start-text"><!--[-->Your public profile URL<!--]--></span
    ><span class="ui-field"
    ><input type="text" placeholder="example.com" /><span class="ui-prefix"
    ><!--[-->https://<!--]--></span
    ><!----><!----><!----></span
    ><span class="ui-end-text"
    ><!--[-->Must include a valid domain<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    ><label class="ui-text-field ui-spread"
    ><span class="ui-label"><!--[-->Project name<!--]--></span
    ><span class="ui-start-text"
    ><!--[-->Used to generate the project URL<!--]--></span
    ><span class="ui-field"
    ><input type="text" placeholder="my-project" /><!----><!----><span
    class="ui-header"
    ><!--[-->acme.dev/<!--]--></span
    ><span class="ui-footer"
    ><!--[-->Lowercase letters and dashes only<!--]--></span
    ></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled ui-spread"
    ><span class="ui-label"><!--[-->API key<!--]--></span
    ><span class="ui-start-text"><!--[-->Stored encrypted at rest<!--]--></span
    ><span class="ui-field"
    ><input type="password" placeholder="Paste your key" /><span
    class="ui-prefix"
    ><!--[--><svg
    width="16"
    height="16"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
    <path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg
    ><!--]--></span
    ><!----><span class="ui-header"><!--[-->Secret<!--]--></span
    ><span class="ui-footer"><!--[-->Rotates every 90 days<!--]--></span></span
    ><span class="ui-end-text"
    ><!--[-->Treat like a password<!--]--><!--[--><!--]--></span
    ><!--[--><!--]--></label
    ><!--]-->

    Auto-fit

    When enabled the Field changes size depending on its content.

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Label" placeholder="Auto-fit" autoFit />
    </template>
    <label class="ui-text-field ui-auto-fit"
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    placeholder="Auto-fit"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    >

    Input types

    <script setup lang="ts"></script>
    <template>
    <div class="example-column">
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Color</span>
    <span class="ui-field">
    <input type="color" placeholder="Color" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Email</span>
    <span class="ui-field">
    <input type="email" placeholder="name@email.com" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Password</span>
    <span class="ui-field">
    <input type="password" placeholder="Password" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Search</span>
    <span class="ui-field">
    <input type="search" placeholder="Search" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Phone</span>
    <span class="ui-field">
    <input type="tel" placeholder="(666) 666-1337" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Text</span>
    <span class="ui-field">
    <input type="text" placeholder="Text" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">URL</span>
    <span class="ui-field">
    <input type="url" placeholder="https://yoursite.com" />
    </span>
    </label>
    </div>
    <div class="example-column">
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Date</span>
    <span class="ui-field">
    <input type="date" placeholder="Date" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Datetime local</span>
    <span class="ui-field">
    <input type="datetime-local" placeholder="Datetime local" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Month</span>
    <span class="ui-field">
    <input type="month" placeholder="Month" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Time</span>
    <span class="ui-field">
    <input type="time" placeholder="Time" />
    </span>
    </label>
    <label class="ui-text-field input-type-field">
    <span class="ui-label">Week</span>
    <span class="ui-field">
    <input type="week" placeholder="Week" />
    </span>
    </label>
    </div>
    </template>
    <!--[-->
    <div class="example-column">
    <label class="ui-text-field input-type-field"
    ><span class="ui-label">Color</span
    ><span class="ui-field"
    ><input type="color" placeholder="Color" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Email</span
    ><span class="ui-field"
    ><input type="email" placeholder="name@email.com" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Password</span
    ><span class="ui-field"
    ><input type="password" placeholder="Password" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Search</span
    ><span class="ui-field"
    ><input type="search" placeholder="Search" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Phone</span
    ><span class="ui-field"
    ><input type="tel" placeholder="(666) 666-1337" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Text</span
    ><span class="ui-field"
    ><input type="text" placeholder="Text" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">URL</span
    ><span class="ui-field"
    ><input type="url" placeholder="https://yoursite.com" /></span
    ></label>
    </div>
    <div class="example-column">
    <label class="ui-text-field input-type-field"
    ><span class="ui-label">Date</span
    ><span class="ui-field"
    ><input type="date" placeholder="Date" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Datetime local</span
    ><span class="ui-field"
    ><input
    type="datetime-local"
    placeholder="Datetime local" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Month</span
    ><span class="ui-field"
    ><input type="month" placeholder="Month" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Time</span
    ><span class="ui-field"
    ><input type="time" placeholder="Time" /></span></label
    ><label class="ui-text-field input-type-field"
    ><span class="ui-label">Week</span
    ><span class="ui-field"><input type="week" placeholder="Week" /></span
    ></label>
    </div>
    <!--]-->

    Date inputs

    Date-related inputs never show as empty, so the label is always visible. There are only hacks with compromises and no neat ways of dealing with that issue. You're free to implement a solution of your own here that works with your project.

    Numeric vs <input type="number">

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Numeric" placeholder="Numeric" type="numeric" />
    </template>
    <label class="ui-text-field"
    ><span class="ui-label"><!--[-->Numeric<!--]--></span><!----><span
    class="ui-field"
    ><input
    inputmode="numeric"
    pattern="[0-9]*"
    type="text"
    placeholder="Numeric"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    >

    You most likely don't need <input type="number">

    While <input type="number"> may seem logical for numeric data it should only be used when mathematical operations are needed on the input (which is... never). Data like credit card numbers, IDs or social security numbers - are actually text that happen to be numeric rather than mathematical values. Therefore, consider using<input type="text" inputmode="numeric" pattern="[0-9]*"> instead.

    You will have a bad time.

    This triggers the numeric keyboard on mobile devices while avoiding the jank of number inputs, such as:

    • Unexpected value increments from scroll wheels
    • Browser-specific validation differences
    • Accessibility problems
    • Removal of leading zeros
    • Allows for some non-numeric mathematical characters

    It should probably be called <input type="math"> instead.

    The British Government has a great article about how bad input number is and goes in-depth. It's a very interesting read.

    File

    Use aria-label instead of the <label> element.

    File is a weird one. Should it really be an <input> element? Well, it's what we've got :sweat_smile:

    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField type="file" placeholder="File" label="Label" />
    <TextField type="file" placeholder="File" label="Label" filled />
    </template>
    <!--[--><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input type="file" placeholder="File" /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><label class="ui-text-field ui-filled"
    ><span class="ui-label"><!--[-->Label<!--]--></span><!----><span
    class="ui-field"
    ><input type="file" placeholder="File" /><!----><!----><!----><!----></span
    ><!----><!--[--><!--]--></label
    ><!--]-->

    Autosuggest

    Leverages the <input> + <datalist> element combo.

    • <input list="DATALISTID">
    • <datalist id="DATALISTID">
    <script setup lang="ts">
    import { TextField } from "opui-css/vue"
    </script>
    <template>
    <TextField label="Users" list="users" placeholder="Placeholder">
    <datalist id="users">
    <option value="Ray Manzarek"></option>
    <option value="Jonny Greenwood"></option>
    <option value="Marika Hackman"></option>
    </datalist>
    </TextField>
    <TextField
    filled
    label="Emails"
    list="users-email"
    placeholder="Placeholder"
    type="email"
    >
    <datalist id="users-email">
    <option value="ray.manzarek@the.doors"></option>
    <option value="jonny.greenwood@radio.head"></option>
    <option value="marika@hack.man"></option>
    </datalist>
    </TextField>
    </template>
    <!--[--><label class="ui-text-field"
    ><span class="ui-label"><!--[-->Users<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="text"
    list="users"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><datalist id="users">
    <option value="Ray Manzarek"></option>
    <option value="Jonny Greenwood"></option>
    <option value="Marika Hackman"></option></datalist
    ><!--]--></label
    ><label class="ui-text-field ui-filled"
    ><span class="ui-label"><!--[-->Emails<!--]--></span><!----><span
    class="ui-field"
    ><input
    type="email"
    list="users-email"
    placeholder="Placeholder"
    /><!----><!----><!----><!----></span
    ><!----><!--[--><datalist id="users-email">
    <option value="ray.manzarek@the.doors"></option>
    <option value="jonny.greenwood@radio.head"></option>
    <option value="marika@hack.man"></option></datalist
    ><!--]--></label
    ><!--]-->

    Think of <datalist> as a list of suggested values.

    • <select> only allows you to choose between its provided values.
    • <input> lets you input anything you want.
    • <input> + <datalist> is a hybrid between the two.

    Do I have to use <label>?

    No. But you get some accessibility wins for free with <label>. It's recommended to label your inputs somehow.

    <template>
    <div class="ui-text-field">
    <span class="ui-field">
    <input type="text" placeholder="Placeholder" />
    </span>
    </div>
    </template>
    <div class="ui-text-field">
    <span class="ui-field"><input type="text" placeholder="Placeholder" /></span>
    </div>

    Accessibility

    API

    Text field API

    PropTypeDefaultDescription
    autoFitbooleanfalseChanges size depending on its content.
    descriptionstring-Description text displayed above the field.
    endTextstring-Supporting text displayed below the field.
    errorbooleanfalseShows error styles.
    filledbooleanfalseThe variant to use.
    idstring-The id of the <input>.
    labelstring-The label for the field.
    smallbooleanfalseThe size of the element.
    spreadbooleanfalsePushes the label and description to one side and the input to the other.
    startTextstring-Legacy alias of description.
    type"numeric", string"text"The input type. "numeric" renders a text input with a numeric keyboard.
    v-modelstring, number-The input value.

    Slots

    SlotDescription
    defaultExtra content inside the root, such as a <datalist>.
    descriptionDescription text displayed above the field.
    end-textSupporting text displayed below the field.
    footerContent below the input, inside the border, with a divider.
    headerContent above the input, inside the border, with a divider.
    labelThe label for the field.
    prefixContent at the inline-start of the field, inside the border.
    suffixContent at the inline-end of the field, inside the border.
    supporting-textLegacy alias of the end-text slot.

    Attributes that aren't props, such as placeholder or disabled, go to the <input>.

    Text input API

    TypeModifiersDefaultDescription
    Wrapper.ui-field-The <input> must be wrapped in a <span class="ui-field"> element. Border, background, and focus styling are inherited from .ui-field, not the input itself.
    Auto-fit.ui-auto-fit-When enabled, the element changes size depending on its content.
    Sizes.ui-small-The size of the element.
    Variantsdefault, .ui-filled-The variant to use.

    Browser support

    Full supportSupported since v125.Full supportSupported since v152.Full supportSupported since v26.2.

    See also the full browser support guide.

    Installation

    @layer components.extended {
    /*
    - Common styling for input, textarea and select
    - Form related styles such as: label, supporting text, error handling
    */
    :where(.ui-text-field, .ui-textarea, .ui-select) {
    --_motion: var(--motion, 1);
    --_accent-color: var(--primary);
    --_bg-color: var(--surface-default);
    --_border-color: var(--field-border-color);
    --_field-padding-block: var(--size-2);
    --_field-padding-inline: var(--size-2);
    --_filled-border-color: var(--text-primary);
    --_height: var(--field-size);
    --_label-color: var(--text-muted);
    --_end-text-color: var(--text-muted);
    display: grid;
    gap: var(--size-1) 0;
    position: relative;
    /* Field - the actual input with a border around it */
    .ui-field {
    background-color: var(--_bg-color);
    border: var(--field-border-width) solid var(--_border-color);
    border-radius: var(--field-border-radius);
    display: grid;
    grid-column: 1/-1;
    grid-row: 2;
    grid-template-areas:
    "header header header"
    "prefix input suffix"
    "footer footer footer";
    grid-template-columns: auto 1fr auto;
    min-block-size: var(--_height);
    transition: border-color calc(0.2s * var(--_motion))
    cubic-bezier(0.4, 0, 0.2, 1);
    }
    :where(.ui-field) :where(input, textarea, select) {
    background: transparent;
    border: 0;
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: var(--font-size-1);
    grid-area: input;
    inline-size: 100%;
    line-height: var(--font-lineheight-1);
    min-inline-size: 0;
    padding: var(--_field-padding-block) var(--_field-padding-inline);
    &:focus,
    &:focus-visible {
    outline: 0;
    }
    }
    /* Affixes */
    .ui-prefix,
    .ui-suffix {
    align-items: center;
    color: var(--_label-color);
    display: inline-flex;
    padding-inline: var(--_field-padding-inline);
    white-space: nowrap;
    }
    .ui-prefix {
    grid-area: prefix;
    }
    .ui-suffix {
    grid-area: suffix;
    }
    .ui-header,
    .ui-footer {
    align-items: center;
    color: var(--_label-color);
    display: flex;
    font-size: var(--font-size-0);
    gap: var(--size-2);
    padding: var(--size-1) var(--_field-padding-inline);
    }
    .ui-header {
    border-block-end: 1px solid var(--_border-color);
    grid-area: header;
    }
    .ui-footer {
    border-block-start: 1px solid var(--_border-color);
    grid-area: footer;
    }
    /* Remove padding on the input when affixes are next to it */
    .ui-field:has(> .ui-prefix) :where(input, textarea, select) {
    padding-inline-start: 0;
    }
    .ui-field:has(> .ui-suffix) :where(input, textarea, select) {
    padding-inline-end: 0;
    }
    /* Textarea - keep affix aligned with the first line */
    &.ui-textarea .ui-field {
    .ui-prefix,
    .ui-suffix {
    align-self: start;
    padding-block: var(--_field-padding-block);
    }
    }
    /* Required/Invalid */
    &:has(:invalid) {
    .ui-label:after {
    color: var(--red);
    content: "*";
    margin: -0.25em auto auto 0.25em;
    }
    }
    /* File */
    &:has(input[type="file"]) {
    cursor: pointer;
    input {
    align-self: flex-start;
    block-size: var(--_height);
    box-shadow: none;
    color: var(--text-primary);
    cursor: inherit;
    max-inline-size: 100%;
    padding: 0;
    transition: font-size 0.2s var(--ease-3);
    &::-webkit-file-upload-button,
    &::file-selector-button {
    background-color: var(--surface-tonal);
    block-size: calc(100% - var(--size-2) * 2);
    border: none;
    border-radius: var(--field-border-radius);
    cursor: pointer;
    margin-block-start: var(--size-2);
    margin-inline-end: 1ex;
    margin-inline-start: var(--size-2);
    }
    }
    /* Variants */
    &.ui-filled {
    input {
    &::-webkit-file-upload-button,
    &::file-selector-button {
    background-color: var(--surface-default);
    block-size: calc(100% - var(--size-2) * 2);
    border-radius: var(--field-border-radius);
    cursor: pointer;
    margin-block-start: var(--size-2);
    }
    }
    }
    /* Sizes */
    &.ui-small {
    input {
    font-size: var(--font-size-05);
    &::-webkit-file-upload-button,
    &::file-selector-button {
    block-size: calc(100% - var(--size-2));
    margin-block-start: var(--size-1);
    }
    }
    }
    }
    /* Autosuggest */
    &:has(input[list]) {
    .ui-label {
    /* Make sure chevron is visible */
    inline-size: calc(100% - var(--size-6));
    }
    }
    /* Select */
    &:has(select) {
    .ui-label {
    /* Make sure chevron is visible */
    inline-size: calc(100% - var(--size-6));
    }
    }
    /* Customizable Select */
    &:has(select button) {
    select {
    padding: 0;
    button {
    outline: 0;
    padding: var(--_field-padding-block) var(--size-8)
    var(--_field-padding-block) var(--_field-padding-inline);
    }
    }
    }
    /* Non-customizable Select */
    &:has(select):not(:has(button)) {
    select {
    padding: var(--_field-padding-block) var(--size-8)
    var(--_field-padding-block) var(--_field-padding-inline);
    }
    }
    /* Input - color */
    &:has(input[type="color"]) {
    .ui-field {
    block-size: var(--_height);
    inline-size: var(--_height);
    min-block-size: 0;
    overflow: hidden;
    }
    input {
    appearance: none;
    background: none;
    block-size: var(--_height);
    inline-size: var(--_height);
    overflow: hidden;
    padding: 0;
    &::-webkit-color-swatch {
    border: none;
    }
    &::-webkit-color-swatch-wrapper {
    padding: 0;
    }
    }
    }
    /* Element states */
    &:hover {
    &:not([data-invalid]) {
    .ui-field {
    border-color: var(--text-primary);
    }
    }
    }
    &:focus-within {
    .ui-field {
    border-color: var(--_accent-color);
    outline-offset: -2px;
    }
    }
    /* Label */
    .ui-label {
    color: var(--text-primary);
    font-size: var(--font-size-05);
    font-weight: 600;
    grid-column: 1/-1;
    grid-row: 1;
    padding-inline: 0 1ex;
    }
    :where(.ui-start-text) {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1/-1;
    grid-row: 2;
    line-height: 1.5;
    }
    &:not(.ui-spread):has(> .ui-start-text) {
    .ui-field {
    grid-row: 3;
    }
    :where(.ui-end-text) {
    grid-row: 4;
    }
    }
    /* End text */
    :where(.ui-end-text) {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1/-1;
    grid-row: 3;
    line-height: 1.5;
    }
    /* Auto-fit */
    &.ui-auto-fit {
    inline-size: auto;
    .ui-field {
    inline-size: auto;
    }
    :where(& input, & textarea) {
    field-sizing: content;
    inline-size: auto;
    min-inline-size: 25ch;
    }
    }
    /* Validation */
    &[data-invalid],
    &:has(:user-invalid) {
    --_accent-color: var(--color-9);
    --_border-color: var(--color-9);
    --_filled-border-color: var(--color-9);
    --_label-color: var(--color-9);
    --_end-text-color: var(--color-9);
    }
    /*
    * Variant: Filled
    */
    &.ui-filled {
    --_bg-color: var(--surface-tonal);
    &:not(:has([disabled], :has(input[type="color"]))) {
    /* Hover */
    &:hover {
    --_bg-color: light-dark(
    oklch(from var(--surface-tonal) calc(l * 0.93) c h),
    oklch(from var(--surface-tonal) calc(l * 1.1) c h)
    );
    }
    }
    }
    /* Disabled */
    &:where(:has([disabled])) {
    cursor: not-allowed;
    opacity: 0.64;
    user-select: none;
    &::before {
    display: none;
    }
    .ui-field {
    cursor: not-allowed;
    }
    :where(input, textarea, select) {
    cursor: not-allowed;
    * {
    pointer-events: none;
    }
    }
    .ui-label,
    .ui-start-text,
    .ui-end-text {
    cursor: not-allowed;
    }
    }
    /* Read-only */
    &:where(:has([readonly])) {
    &::before {
    display: none;
    }
    :where(input, textarea, select) {
    cursor: not-allowed;
    * {
    pointer-events: none;
    }
    }
    }
    /* Sizes */
    &.ui-small {
    --_field-padding-block: var(--size-2);
    --_height: var(--field-size-small);
    &:has(input[type="color"]) {
    .ui-label {
    line-height: 1.5;
    }
    }
    }
    /* Orientation */
    &.ui-spread {
    align-items: start;
    column-gap: var(--size-4);
    container-type: inline-size;
    grid-template-columns: 1fr auto;
    .ui-label {
    font-weight: 600;
    grid-column: 1;
    grid-row: 1;
    }
    .ui-start-text {
    color: var(--_end-text-color);
    font-size: var(--font-size-0);
    grid-column: 1;
    grid-row: 2;
    line-height: 1.5;
    }
    .ui-field {
    align-self: start;
    grid-column: 2;
    grid-row: 1 / span 2;
    }
    .ui-field:has(textarea) {
    grid-row: 1 / span 3;
    + .ui-end-text {
    grid-row: 4;
    }
    }
    :where(textarea) {
    min-inline-size: 30ch;
    }
    :where(.ui-end-text) {
    grid-column: 2;
    text-align: end;
    }
    /* Collapse to column on narrow containers */
    @container (width < 400px) {
    column-gap: 0;
    grid-template-columns: 1fr;
    .ui-label {
    grid-column: 1;
    grid-row: 1;
    }
    .ui-start-text {
    grid-column: 1;
    grid-row: 2;
    }
    .ui-field,
    .ui-field:has(textarea) {
    grid-column: 1;
    grid-row: 3;
    }
    :where(.ui-end-text) {
    grid-column: 1;
    grid-row: 4;
    text-align: start;
    }
    }
    }
    }
    }
    @layer components.extended {
    :where(
    .ui-text-field:has(
    :where(
    input[type="date"],
    input[type="datetime-local"],
    input[type="email"],
    input[type="month"],
    input[type="number"],
    input[type="password"],
    input[type="search"],
    input[type="tel"],
    input[type="text"],
    input[type="time"],
    input[type="url"],
    input[type="week"]
    )
    )
    ) {
    /* Sizes */
    &.ui-small {
    input {
    padding-inline: var(--size-2);
    }
    }
    }
    /* Autosuggest */
    :where(.ui-text-field:has(input[list])) {
    .ui-field {
    position: relative;
    }
    /* Hide native arrow */
    input[list]::-webkit-calendar-picker-indicator {
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    position: absolute;
    }
    input[list] {
    padding-inline-end: var(--size-8);
    }
    .ui-field::after {
    block-size: 0;
    border-block-start: 5px solid;
    border-inline: 5px solid transparent;
    content: "";
    inline-size: 0;
    inset: 50% var(--size-3) auto auto;
    pointer-events: none;
    position: absolute;
    translate: 0 -50%;
    }
    &.ui-small {
    input[list] {
    padding-inline-end: var(--size-7);
    }
    .ui-field::after {
    inset-inline-end: var(--size-2);
    }
    }
    }
    }