Input
Input collects one line of text. Give every input a visible label or an aria-label. For several lines of text, use Textarea.
'use client';
import { Input } from '@/components/nxds/components/input/input';
export default function InputTypeEmail() {
return (
<div className="nx:w-[400px]">
<Input type="email" placeholder="email@example.com" />
</div>
);
}
Installation
npx nxds-cli@latest add input
Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.
Usage
import { Input } from '@/components/nxds/components/input/input';
Examples
With label
Link a Label to the input with htmlFor and a matching id.
'use client';
import * as React from 'react';
import { Input } from '@/components/nxds/components/input/input';
import { Label } from '@/components/nxds/components/label/label';
export default function InputWithLabel() {
const inputId = React.useId();
return (
<div className="nx:w-[400px]">
<div className="nx:grid nx:gap-1.5">
<Label htmlFor={inputId}>Full name</Label>
<Input id={inputId} placeholder="Ada Lovelace" />
</div>
</div>
);
}
This example also needs: Label.
Disabled
Set disabled to block editing and dim the field.
'use client';
import { Input } from '@/components/nxds/components/input/input';
export default function InputDisabled() {
return (
<div className="nx:w-[400px]">
<Input placeholder="Disabled input" disabled />
</div>
);
}
Invalid
Set aria-invalid to show the error border.
'use client';
import { Input } from '@/components/nxds/components/input/input';
export default function InputInvalid() {
return (
<div className="nx:w-[400px]">
<Input defaultValue="invalid@" aria-invalid aria-label="Invalid email" />
</div>
);
}
All variants
Compare every size, variant, state and type side by side.
Sizes
Variants
States
Types
'use client';
import { Input } from '@/components/nxds/components/input/input';
export default function InputAllVariants() {
return (
<div className="nx:w-[400px]">
<div className="nx:flex nx:flex-col nx:gap-8 nx:w-[400px]">
<div>
<h3 className="nx:text-foreground nx:mb-4 nx:typography-label-default">
Sizes
</h3>
<div className="nx:flex nx:flex-col nx:gap-3">
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
sm
</span>
<Input size="sm" placeholder="Small input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
default
</span>
<Input size="default" placeholder="Default input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
lg
</span>
<Input size="lg" placeholder="Large input" />
</div>
</div>
</div>
<div>
<h3 className="nx:text-foreground nx:mb-4 nx:typography-label-default">
Variants
</h3>
<div className="nx:flex nx:flex-col nx:gap-3">
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-20">
bordered
</span>
<Input placeholder="Bordered input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-20">
borderless
</span>
<Input variant="borderless" placeholder="Borderless input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-20">
ghost
</span>
<Input variant="ghost" placeholder="Ghost input" />
</div>
</div>
</div>
<div>
<h3 className="nx:text-foreground nx:mb-4 nx:typography-label-default">
States
</h3>
<div className="nx:flex nx:flex-col nx:gap-3">
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
empty
</span>
<Input placeholder="Placeholder text" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
filled
</span>
<Input defaultValue="Filled value" aria-label="Filled input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
disabled
</span>
<Input placeholder="Disabled" disabled />
</div>
</div>
</div>
<div>
<h3 className="nx:text-foreground nx:mb-4 nx:typography-label-default">
Types
</h3>
<div className="nx:flex nx:flex-col nx:gap-3">
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
text
</span>
<Input type="text" placeholder="Text input" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
email
</span>
<Input type="email" placeholder="email@example.com" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
password
</span>
<Input type="password" placeholder="••••••••" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
number
</span>
<Input type="number" placeholder="0" />
</div>
<div className="nx:flex nx:items-center nx:gap-4">
<span className="nx:typography-label-small nx:text-muted-foreground nx:w-16">
file
</span>
<Input type="file" aria-label="File upload" />
</div>
</div>
</div>
</div>
</div>
);
}
Props
Input
| Prop | Type | Default | Description |
|---|---|---|---|
size | "default" | "sm" | "lg" | null | "default" | —No description |
variant | "ghost" | "borderless" | "bordered" | null | "bordered" | —No description |
Input collects one line of text. Give every input a visible label or an aria-label. For several lines of text, use Textarea.
<script setup lang="ts">
import { Input } from '@/components/input';
</script>
<template>
<Input type="email" placeholder="email@example.com" />
</template>
Installation
- 1
Install the following dependencies:
npm install @vueuse/core@^14.1.0 class-variance-authority@^0.7.1 - 2
Copy and paste the following code into your project.
components/input/Input.vue <script setup lang="ts"> import { useVModel } from '@vueuse/core'; import type { HTMLAttributes } from 'vue'; import { cn } from '../../lib/utils'; import { type InputVariants, inputVariants } from '.'; export interface InputProps { size?: InputVariants['size']; variant?: InputVariants['variant']; /** The value, for `v-model`. */ modelValue?: string | number; /** The starting value when `v-model` isn't bound. */ defaultValue?: string | number; class?: HTMLAttributes['class']; } const props = withDefaults(defineProps<InputProps>(), { size: 'default', variant: 'bordered', }); const emits = defineEmits<{ 'update:modelValue': [value: string | number]; }>(); const model = useVModel(props, 'modelValue', emits, { passive: true, defaultValue: props.defaultValue, }); </script> <template> <input :value="model" data-slot="input" :data-size="size" :data-variant="variant" :class="cn(inputVariants({ size, variant }), props.class)" @input="model = ($event.target as HTMLInputElement).value" /> </template>components/input/index.ts import { cva, type VariantProps } from 'class-variance-authority'; import { fieldSizeVariants } from '../../lib/field-size'; export { default as Input, type InputProps } from './Input.vue'; export const inputVariants = cva( [ 'nx:flex nx:box-border nx:w-full nx:rounded-md nx:border-default', 'nx:text-foreground nx:autofill-text-foreground nx:transition-field', 'nx:file:border-0 nx:file:bg-transparent nx:file:typography-label-default nx:file:text-foreground nx:disabled:file:text-disabled-foreground', 'nx:placeholder:text-muted-foreground', 'nx:focus-visible:outline-default nx:focus-visible:outline-focus-default nx:focus-visible:border-focus-default', 'nx:aria-invalid:border-error-border nx:aria-invalid:focus-visible:outline-focus-error nx:aria-invalid:focus-visible:border-focus-error', 'nx:disabled:cursor-not-allowed nx:disabled:bg-disabled nx:disabled:autofill-bg-disabled nx:disabled:text-disabled-foreground nx:disabled:autofill-text-disabled-foreground nx:disabled:placeholder:text-disabled-foreground', ], { variants: { size: fieldSizeVariants, variant: { bordered: 'nx:border-border-default nx:bg-container nx:autofill-bg-container nx:enabled:hover:bg-container-hover nx:enabled:hover:autofill-bg-container-hover nx:disabled:border-border-disabled', borderless: 'nx:border-transparent nx:bg-control-background nx:autofill-bg-control-background nx:enabled:hover:bg-control-background-hover nx:enabled:hover:autofill-bg-control-background-hover', ghost: 'nx:border-transparent nx:bg-transparent nx:autofill-bg-transparent nx:enabled:hover:not-aria-invalid:not-focus-visible:border-border-default', }, }, defaultVariants: { size: 'default', variant: 'bordered', }, } ); export type InputVariants = VariantProps<typeof inputVariants>;lib/field-size.ts export const fieldSizeVariants = { default: 'nx:h-10 nx:px-3 nx:py-0 nx:typography-body-default', sm: 'nx:h-8 nx:px-2.5 nx:py-0 nx:typography-body-small', lg: 'nx:h-12 nx:px-3.5 nx:py-0 nx:typography-body-default', }; - 3
Keep each file at the path shown above it.
Paths are relative to
packages/vue/src/. Keep the same folders under your@/root, and the imports between files work without edits.
Usage
import { Input } from '@/components/input';
Examples
With label
Link a Label to the input with for and a matching id.
<script setup lang="ts">
import { useId } from 'vue';
import { Input } from '@/components/input';
import { Label } from '@/components/label';
const inputId = useId();
</script>
<template>
<div class="nx:grid nx:gap-1.5">
<Label :for="inputId">Full name</Label>
<Input :id="inputId" placeholder="Ada Lovelace" />
</div>
</template>
This example also needs: Label.
Disabled
Set disabled to block editing and dim the field.
<script setup lang="ts">
import { Input } from '@/components/input';
</script>
<template>
<Input placeholder="Disabled input" disabled />
</template>
Invalid
Set aria-invalid to show the error border.
<script setup lang="ts">
import { Input } from '@/components/input';
</script>
<template>
<Input
default-value="invalid@"
aria-invalid="true"
aria-label="Invalid email"
/>
</template>
All variants
Compare every size, variant, state and type side by side.
<script setup lang="ts">
import { Input } from '@/components/input';
</script>
<template>
<div class="nx:flex nx:flex-col nx:gap-8 nx:w-[400px]">
<div>
<h3 class="nx:text-foreground nx:mb-4 nx:typography-label-default">
Sizes
</h3>
<div class="nx:flex nx:flex-col nx:gap-3">
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
sm
</span>
<Input size="sm" placeholder="Small input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
default
</span>
<Input size="default" placeholder="Default input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
lg
</span>
<Input size="lg" placeholder="Large input" />
</div>
</div>
</div>
<div>
<h3 class="nx:text-foreground nx:mb-4 nx:typography-label-default">
Variants
</h3>
<div class="nx:flex nx:flex-col nx:gap-3">
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-20"
>
bordered
</span>
<Input placeholder="Bordered input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-20"
>
borderless
</span>
<Input variant="borderless" placeholder="Borderless input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-20"
>
ghost
</span>
<Input variant="ghost" placeholder="Ghost input" />
</div>
</div>
</div>
<div>
<h3 class="nx:text-foreground nx:mb-4 nx:typography-label-default">
States
</h3>
<div class="nx:flex nx:flex-col nx:gap-3">
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
empty
</span>
<Input placeholder="Placeholder text" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
filled
</span>
<Input default-value="Filled value" aria-label="Filled input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
disabled
</span>
<Input placeholder="Disabled" disabled />
</div>
</div>
</div>
<div>
<h3 class="nx:text-foreground nx:mb-4 nx:typography-label-default">
Types
</h3>
<div class="nx:flex nx:flex-col nx:gap-3">
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
text
</span>
<Input type="text" placeholder="Text input" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
email
</span>
<Input type="email" placeholder="email@example.com" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
password
</span>
<Input type="password" placeholder="••••••••" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
number
</span>
<Input type="number" placeholder="0" />
</div>
<div class="nx:flex nx:items-center nx:gap-4">
<span
class="nx:typography-label-small nx:text-muted-foreground nx:w-16"
>
file
</span>
<Input type="file" aria-label="File upload" />
</div>
</div>
</div>
</div>
</template>
Props
Input
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | string | number | —No default documented | The starting value when |
modelValue | string | number | —No default documented | The value, for |
size | "default" | "sm" | "lg" | null | "default" | —No description |
variant | "ghost" | "bordered" | "borderless" | null | "bordered" | —No description |