Input
A text input field with platform-adaptive styling.
A text input — a floating label on Material, an inline label on iOS.
Installation
npx touchcn add input
Usage
<tcn-input label="Email" type="email" [(value)]="email" />import { TcnInput } from '@/components/ui/input';
<TcnInput label="Email" type="email" onValueChange={setEmail} /><script setup lang="ts">
import { TcnInput } from '@/components/ui/input';
</script>
<template>
<TcnInput v-model="email" label="Email" type="email" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
— | Floating (MD) / inline (iOS) field label. |
value |
string (model, Angular) |
'' |
Two-way bound value. |
placeholder |
string |
— | Placeholder text. |
type |
string |
'text' |
Native input type. |
error |
boolean |
false |
Tint the outline / border destructive. |
disabled |
boolean |
false |
Disable the field. |
onValueChange |
(value: string) => void |
— | Convenience callback with the raw value (React). |
Source · Angular
export * from './tcn-input';import { booleanAttribute, Component, input, model } from '@angular/core';
@Component({
selector: 'tcn-input',
template: `
<label
class="tcn-input relative flex items-center gap-3 px-3 py-2.5"
[class.tcn-input-labeled]="!!label()"
[attr.data-error]="error() || null"
>
@if (label()) {
<span class="tcn-input-label">{{ label() }}</span>
}
<input
class="w-full bg-transparent text-[var(--color-on-surface)] outline-none"
[type]="type()"
[placeholder]="placeholder()"
[disabled]="disabled()"
[value]="value()"
(input)="value.set($any($event.target).value)"
/>
<fieldset class="tcn-input-outline" aria-hidden="true">
@if (label()) {
<legend class="tcn-input-notch">{{ label() }}</legend>
}
</fieldset>
</label>
`,
host: { class: 'block' },
})
export class TcnInput {
readonly value = model('');
readonly label = input('');
readonly placeholder = input('');
readonly type = input('text');
readonly disabled = input(false, { transform: booleanAttribute });
readonly error = input(false, { transform: booleanAttribute });
}Source · React
export * from './tcn-input';import { forwardRef } from 'react';
import type { ChangeEvent, ComponentPropsWithoutRef } from 'react';
import { cn } from '@touchcn/core';
export interface TcnInputProps extends ComponentPropsWithoutRef<'input'> {
/** Floating (MD) / inline (iOS) field label. */
label?: string;
/** Error state — tints the outline / border destructive. */
error?: boolean;
/** Wrapper class applied to the field host. */
wrapperClassName?: string;
/** Convenience callback with the raw string value. */
onValueChange?(value: string): void;
}
export const TcnInput = forwardRef<HTMLInputElement, TcnInputProps>(function TcnInput(
{ label, error, className, wrapperClassName, onValueChange, onChange, ...props },
ref,
) {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange?.(event);
onValueChange?.(event.target.value);
};
return (
<div className={cn('block', wrapperClassName)}>
<label
className={cn('tcn-input relative flex items-center gap-3 px-3 py-2.5', label && 'tcn-input-labeled')}
data-error={error || undefined}
>
{label && <span className="tcn-input-label">{label}</span>}
<input
ref={ref}
className={cn('w-full bg-transparent text-[var(--color-on-surface)] outline-none', className)}
onChange={handleChange}
{...props}
/>
<fieldset className="tcn-input-outline" aria-hidden="true">
{label && <legend className="tcn-input-notch">{label}</legend>}
</fieldset>
</label>
</div>
);
});Source · Vue
<script setup lang="ts">
import { cn } from '@touchcn/core';
defineOptions({ inheritAttrs: false });
const props = defineProps<{
/** Floating (MD) / inline (iOS) field label. */
label?: string;
/** Error state — tints the outline / border destructive. */
error?: boolean;
/** Wrapper class applied to the field host. */
wrapperClass?: string;
}>();
/** Two-way bound raw string value. */
const model = defineModel<string>();
</script>
<template>
<div :class="cn('block', props.wrapperClass)">
<label
:class="cn('tcn-input relative flex items-center gap-3 px-3 py-2.5', label && 'tcn-input-labeled')"
:data-error="error || undefined"
>
<span v-if="label" class="tcn-input-label">{{ label }}</span>
<input
v-model="model"
class="w-full bg-transparent text-[var(--color-on-surface)] outline-none"
v-bind="$attrs"
/>
<fieldset class="tcn-input-outline" aria-hidden="true">
<legend v-if="label" class="tcn-input-notch">{{ label }}</legend>
</fieldset>
</label>
</div>
</template>export { default as TcnInput } from './TcnInput.vue';