Skip to content

CTextField

CTextField is the primary text input component. Built on top of CInput, it provides a fully styled, accessible, and validatable <input> with a floating label, icon slots, hint text, and theme support.

Usage

Show code
vue
<template>
  <CTextField
    v-model="value"
    id="basic-email"
    label="Email"
    placeholder="Enter your email"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>

States

CTextField supports all standard states: default, disabled, readonly, and clearable.

Hint text below the field
Show code
vue
<template>
  <CTextField v-model="value" label="Default" />
  <CTextField v-model="value" label="Disabled" disabled />
  <CTextField v-model="readonly" label="Readonly" readonly />
  <CTextField v-model="value" label="Clearable" clearable />
</template>

Validation

Pass an array of rule functions via the rules prop. Each rule receives the current value and returns { valid: boolean, message: string }. Use validate-on to control when validation fires: 'input' (default) or 'blur'.

We'll never share your email
Show code
vue
<template>
  <CTextField
    v-model="email"
    label="Email"
    :rules="emailRules"
    validate-on="blur"
    details="We'll never share your email"
  />
  <CTextField
    v-model="password"
    label="Password"
    type="password"
    :rules="passwordRules"
    validate-on="blur"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'

const email = ref('')
const password = ref('')

const emailRules = [
  (v: string) => ({ valid: !!v, message: 'Email is required' }),
  (v: string) => ({ valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), message: 'Invalid email' }),
]

const passwordRules = [
  (v: string) => ({ valid: !!v, message: 'Password is required' }),
  (v: string) => ({ valid: v.length >= 8, message: 'Minimum 8 characters' }),
]
</script>

Prepend, append and details slots

The prepend and append slots place content inside the field borders. The details slot fully replaces the hint/error area.

@
USD
0/20 characters
Show code
vue
<template>
  <!-- Prepend icon -->
  <CTextField v-model="search" label="Search">
    <template #prepend>
      <CIcon name="mdi-magnify" />
    </template>
  </CTextField>

  <!-- Append text -->
  <CTextField v-model="amount" label="Amount" type="number">
    <template #append>
      <span style="opacity: .6; font-size: 13px">USD</span>
    </template>
  </CTextField>

  <!-- Custom details slot -->
  <CTextField
    v-model="nickname"
    label="Nickname"
    :rules="nicknameRules"
    validate-on="input"
  >
    <template #details="{ errorMessage, hasError }">
      <span :style="{ color: hasError ? 'var(--c-app-error-color)' : 'inherit' }">
        {{ errorMessage || `${nickname.length}/20 characters` }}
      </span>
    </template>
  </CTextField>
</template>

Async validation

Rules may return a Promise. While validation is in progress, the details slot receives validating: true.

Min 3 characters, must be unique
We'll send a verification link

Try: admin, user, root (taken) or test@taken.com

Show code
vue
<template>
  <CTextField
    v-model="username"
    label="Username"
    :rules="usernameRules"
    validate-on="blur"
  >
    <template #details="{ errorMessage, hasError, validating }">
      <span v-if="validating" style="color: var(--c-app-primary-color)">
        Checking availability…
      </span>
      <span v-else-if="hasError" style="color: var(--c-app-error-color)">
        {{ errorMessage }}
      </span>
      <span v-else style="opacity: .6">Must be unique</span>
    </template>
  </CTextField>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const username = ref('')
const taken = ['admin', 'user', 'root']

const usernameRules = [
  (v: string) => ({ valid: v.length >= 3, message: 'Minimum 3 characters' }),
  async (v: string) => {
    await new Promise(resolve => setTimeout(resolve, 800))
    return { valid: !taken.includes(v.toLowerCase()), message: `"${v}" is already taken` }
  },
]
</script>

Presets

Presets let you define the field's appearance (label color, border) once during plugin initialization and reuse by name via the preset prop.

Show code
vue
<template>
  <CTextField v-model="value" label="Email" preset="input.blue">
    <template #prepend><CIcon name="fas:envelope" :size="16" source="fa" /></template>
  </CTextField>
</template>

Register presets when initializing the plugin:

ts
import { createVuelandUI } from '@vueland/ui'
import type { CInputPreset } from '@vueland/ui/types'

createVuelandUI({
  presets: {
    input: {
      blue: {
        base:    { label: ['text-blue'] },
        focused: { label: ['text-blue'], field: ['text-blue'] },
        filled:  { label: ['text-blue'] },
        error:   { label: ['text-red'], details: ['text-red'] },
      } satisfies CInputPreset,
    },
  },
})

CInputPreset structure

A preset is a set of flat presets keyed by statebase plus optional per-state overrides. No compound states, no nesting:

ts
type CInputZone = 'root' | 'field' | 'input' | 'label' | 'details' | 'prepend' | 'append'
type CInputState = 'focused' | 'filled' | 'error' | 'disabled' | 'readonly'

type ZonePreset = Partial<Record<CInputZone, string[]>>
type CInputPreset = Partial<Record<'base' | CInputState, ZonePreset>>

The component is in a single current state, and that state's preset is applied — its zones replace base per-zone, no stacking and no priorities. See CInput → Preset system for the full model.

The preset is distributed automatically: CInput applies root and details, and shares the set with CField via provide/inject, which applies field, input, label, prepend, append.


API

Props

PropTypeDefaultDescription
modelValuestring | number | nullundefinedField value (v-model)
idstringauto<input> id. Auto-generated with prefix input- if omitted
labelstringFloating label text
placeholderstringNative <input> placeholder (attr)
detailsstringHint text shown below the field
noDetailsbooleanfalseHide the details area entirely
clearablebooleanfalseShow a clear button when the field has a value and is focused
disabledbooleanfalseDisable the field. Blocks focus and input
readonlybooleanfalseRead-only. Focus is allowed, editing is not
focusedbooleanfalseInitial focused state
rulesValidateFn[][]Array of validation functions
validateOn'input' | 'blur''input'When to trigger automatic validation
presetstringPreset name from Vueland UI configuration
typestring'text'Native <input> type (passed as attr)
namestringNative <input> name attr
autocompletestringNative <input> autocomplete attr
requiredbooleanNative <input> required attr
inputmodestringNative <input> inputmode attr
patternstringNative <input> pattern attr
maxlengthnumberNative <input> maxlength attr
minlengthnumberNative <input> minlength attr
minnumberMin value for type="number"
maxnumberMax value for type="number"
stepnumberStep for type="number"
tabindexnumberNative <input> tabindex attr
enterkeyhintstringMobile keyboard Enter button label
aria-labelstringAdditional aria label (attr)

Slots

SlotPropsDescription
prependContent placed on the left inside the field border
appendContent placed on the right inside the field border
detailsCInputDetailsSlotPropsReplaces the entire hint/error area

details slot props

PropTypeDescription
errorMessagestring | undefinedCurrent error message
hasErrorbooleanWhether there is an active error
validatingbooleanWhether async validation is currently running
uidstringField ID (matches the native <input> id)
detailsstring | undefinedThe value of the details prop

Events

EventArgumentsDescription
update:modelValuestring | number | undefinedValue changed (v-model)
focusField received focus
blurField lost focus

Expose

Methods available via template ref:

MethodSignatureDescription
validate() => Promise<boolean>Trigger validation manually
reset() => voidClear the error state
focus() => voidProgrammatically focus the field
blur() => voidProgrammatically remove focus
vue
<template>
  <CTextField ref="fieldRef" v-model="value" label="Name" :rules="rules" />
  <CBtn @click="fieldRef?.validate()">Validate</CBtn>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const fieldRef = ref()
const value = ref('')
const rules = [(v: string) => ({ valid: !!v, message: 'Required' })]
</script>

ValidateFn type

ts
type ValidateResult = { valid: boolean; message: string }
type ValidateFn = (value: any) => ValidateResult | Promise<ValidateResult>

CSS variables

CInput (root element)

VariableDefaultDescription
--c-input-background-colorvar(--c-app-surface-color)Component background
--c-input-primary-colorvar(--c-app-primary-color)Text color in default state
--c-input-error-colorvar(--c-app-error-color)Text color on error
--c-input-disabled-colorvar(--c-app-disabled-color)Text color when disabled
--c-input-readonly-colorvar(--c-app-primary-color)Text color when readonly
--c-input-readonly-bg-colorgrey lighten-4Field background when readonly
--c-input-field-border-radiusvar(--c-app-border-radius)Field border radius
--c-input-details-height24pxHeight of the details area

CField (border and label)

VariableDefaultDescription
--c-field-border-colorvar(--c-app-border-color, #e5e5e5)Border color
--c-field-disabled-opacity0.5Opacity when disabled

Override example

vue
<CTextField
  v-model="value"
  label="Custom styled"
  style="
    --c-input-primary-color: #7c3aed;
    --c-field-border-color: #ddd6fe;
  "
/>

State CSS classes

ClassCondition
c-input--defaultNo error, not disabled, not readonly
c-input--focusedField is focused
c-input--has-errorValidation error is active
c-input--disableddisabled = true
c-input--readonlyreadonly = true
c-input--clearableclearable = true
c-input--validatingAsync validation is running
c-field--focusedBorder is focused
c-field--filledField has a value (label is raised)
c-field--disabledBorder disabled
c-field--readonlyBorder readonly (dashed)
c-field--has-prependprepend slot is present