Examples
Basic
Preview
Code
<script setup lang="ts">
const value = ref('')
</script>
<template>
<div class="max-w-lg">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
/>
</div>
</template>
Rows and Cols
Prop | Default | Type | Description |
---|---|---|---|
rows | - | number | Set the number of rows for the textarea. |
cols | - | number | Set the number of columns for the textarea. |
Preview
Code
<script setup lang="ts">
const value = ref('')
</script>
<template>
<div class="max-w-lg">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
:rows="10"
:cols="20"
/>
</div>
</template>
Autosizing
Prop | Default | Type | Description |
---|---|---|---|
autoresize | false | boolean number | Enables textarea autosizing. When true , it adjusts height to fit content. When a number , it sets the maximum height to fit content, not exceeding the specified rows. |
Preview
Code
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10')
</script>
<template>
<div class="max-w-lg">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
autoresize
/>
</div>
</template>
Resizing
Prop | Default | Type | Description |
---|---|---|---|
resize | none | none null y x | Change the resize behavior of the textarea. |
Option | Description |
---|---|
none | Prevents the textarea from being resizable. (Default) |
null | Enables both vertical and horizontal resizing. |
y | Allows vertical resizing. |
x | Allows horizontal resizing. |
Preview
Code
<script setup lang="ts">
const value = ref('')
</script>
<template>
<div class="grid max-w-xl gap-2">
<NFormGroup label="Resizable both">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
resize
/>
</NFormGroup>
<NFormGroup label="Resizable horizontal">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
resize="x"
/>
</NFormGroup>
<NFormGroup label="Resizable vertical">
<NInput
v-model="value"
type="textarea"
placeholder="Write your message here..."
resize="y"
/>
</NFormGroup>
</div>
</template>
Read more in form-group component
Slots
Read more in Input component slots
Presets
Read more in Input component slots
Props
types/input.ts
export interface NInputProps {
/**
*
* @default null
*/
type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'textarea' | ''
/**
* Update the input status.
* Useful for validations.
*
* @default null
*/
status?: 'info' | 'success' | 'warning' | 'error'
/**
* Add loading state to the input.
*
* @default false
*/
loading?: boolean
/**
* Swap the position of the leading and trailing icons.
*
* @default false
*/
reverse?: boolean
/**
* Value of the input.
*
* @default null
*/
modelValue?: string | number
/**
* Display leading icon.
*
* @default null
*/
leading?: string
/**
* Display trailing icon.
*
* @default null
*/
trailing?: string
/**
* Allows you to add `UnaUI` input preset properties,
* Think of it as a shortcut for adding options or variants to the preset if available.
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/input.ts
* @example
* input="solid-green"
*/
input?: string
/**
* Allows you to change the size of the input.
*
* @default sm
*
* @example
* size="sm" | size="2cm" | size="2rem" | size="2px"
*/
size?: string
/**
* Manually set the id attribute.
*
* By default, the id attribute is generated randomly for accessibility reasons.
*
* @default randomId
* @example
* id="email"
*/
id?: string
/**
* Automatically resize the textarea to fit the content.
* This property only works with the `textarea` type.
*
* @default false
*/
autoresize?: boolean | number
/**
* This property only works with the `textarea` type.
* You can add your own resize preset or use the default one.
*
* @default none
*
* @example
* resize="x" | resize="y" | resize="none" | null
*/
resize?: string | null
/**
* This property only works with the `textarea` type.
*
* @default 3
*/
rows?: number
/**
* This property only works with the `textarea` type.
*
* @default 3
*/
cols?: number
/**
* `UnaUI` preset configuration
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/input.ts
*/
una?: {
// base
input?: string
inputLoading?: string
inputTrailing?: string
inputLeading?: string
// wrappers
inputWrapper?: string
inputLeadingWrapper?: string
inputTrailingWrapper?: string
// icons
inputWarningIcon?: string
inputErrorIcon?: string
inputSuccessIcon?: string
inputInfoIcon?: string
inputLoadingIcon?: string
}
}
Components
Input.vue
<script setup lang="ts">
import type { NInputProps } from '../../types'
import { computed, onMounted, ref } from 'vue'
import { cn, randomId } from '../../utils'
import NIcon from '../elements/Icon.vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<NInputProps>(), {
type: 'text',
resize: 'none',
rows: 3,
})
const emit = defineEmits(['leading', 'trailing', 'update:modelValue'])
const slots = defineSlots<{
leading?: any
trailing?: any
}>()
const id = computed(() => props.id ?? randomId('input'))
const isLeading = computed(() => props.leading || slots.leading)
const isTrailing = computed(() => props.trailing || slots.trailing || props.status || props.loading)
const inputVariants = ['outline', 'solid'] as const
const hasVariant = computed(() => inputVariants.some(inputVariants => props.input?.includes(inputVariants)))
const isBaseVariant = computed(() => props.input?.includes('~'))
const statusClassVariants = computed(() => {
const input = {
info: 'input-status-info input-solid-info input-status-ring',
success: 'input-status-success input-solid-success input-status-ring',
warning: 'input-status-warning input-solid-warning input-status-ring',
error: 'input-status-error input-solid-error input-status-ring',
default: !hasVariant.value && !isBaseVariant.value ? 'input-default-variant' : '',
}
const text = {
info: 'text-info',
success: 'text-success',
warning: 'text-warning',
error: 'text-error',
default: '',
}
const icon = {
info: props.una?.inputWarningIcon ?? 'input-info-icon',
success: props.una?.inputSuccessIcon ?? 'input-success-icon',
warning: props.una?.inputWarningIcon ?? 'input-warning-icon',
error: props.una?.inputErrorIcon ?? 'input-error-icon',
default: '',
}
return {
input: input[props.status ?? 'default'],
text: text[props.status ?? 'default'],
icon: icon[props.status ?? 'default'],
}
})
const reverseClassVariants = computed(() => {
const input = {
false: [{ 'input-leading-padding': isLeading.value }, { 'input-trailing-padding': isTrailing.value }],
true: [{ 'input-trailing-padding': isLeading.value }, { 'input-leading-padding': isTrailing.value }],
}
return {
input: input[props.reverse ? 'true' : 'false'],
leadingWrapper: props.reverse ? 'input-trailing-wrapper' : 'input-leading-wrapper',
trailingWrapper: props.reverse ? 'input-leading-wrapper' : 'input-trailing-wrapper',
}
})
// html refs
const textarea = ref<HTMLTextAreaElement>()
function resizeTextarea(): void {
if (!(props.type === 'textarea' && props.autoresize) || !textarea.value)
return
textarea.value.rows = props.rows
const styles = window.getComputedStyle(textarea.value)
const paddingTop = Number.parseInt(styles.paddingTop)
const paddingBottom = Number.parseInt(styles.paddingBottom)
const padding = paddingTop + paddingBottom
const lineHeight = Number.parseInt(styles.lineHeight)
const { scrollHeight } = textarea.value
const newRows = (scrollHeight - padding) / lineHeight
if (newRows > props.rows)
textarea.value.rows = newRows
const maxAutoresizeRows = typeof props.autoresize === 'number' ? props.autoresize : Number.POSITIVE_INFINITY
if (textarea.value.rows > maxAutoresizeRows)
textarea.value.rows = maxAutoresizeRows
}
function onInput(event: Event): void {
emit('update:modelValue', (event.target as HTMLInputElement).value)
resizeTextarea()
}
onMounted(() => {
resizeTextarea()
})
</script>
<template>
<div
input="wrapper"
:size="size"
:class="una?.inputWrapper"
>
<div
v-if="isLeading"
:class="[
una?.inputLeadingWrapper,
reverseClassVariants.leadingWrapper,
statusClassVariants.text,
]"
>
<slot name="leading">
<NIcon
v-if="leading"
:name="leading"
input="leading"
:class="una?.inputLeading"
@click="emit('leading')"
/>
</slot>
</div>
<Component
:is="props.type !== 'textarea' ? 'input' : 'textarea'"
:id="id"
ref="textarea"
:value="modelValue"
:type="props.type !== 'textarea' ? props.type : undefined"
class="input"
:class="cn(
type === 'textarea' ? 'input-textarea' : 'input-input',
statusClassVariants.input,
reverseClassVariants.input,
una?.input,
)"
:input="input"
:resize="type === 'textarea' ? resize : undefined"
:rows="type === 'textarea' ? rows : undefined"
:cols="type === 'textarea' ? cols : undefined"
v-bind="$attrs"
@input="onInput"
/>
<div
v-if="isTrailing"
:class="cn(
una?.inputTrailingWrapper,
reverseClassVariants.trailingWrapper,
statusClassVariants.text,
)"
>
<NIcon
v-if="loading"
input="loading"
:name="una?.inputLoadingIcon ?? 'input-loading-icon'"
:class="una?.inputLoading"
/>
<NIcon
v-else-if="status"
input="status-icon-base"
:name="statusClassVariants.icon"
/>
<slot v-else name="trailing">
<NIcon
v-if="trailing"
input="trailing"
:class="una?.inputTrailing"
:name="trailing"
@click="emit('trailing')"
/>
</slot>
</div>
</div>
</template>