Skip to content

TextField — usage examples ​

Not on npm yet

@rozie-ui/text-field is not yet on npm for any framework target — it is still in dogfooding across all six.

TextField is designed to ship as six pre-compiled, per-framework packages from a single .rozie source, but none has published to npm yet. The snippets below show the idiomatic consumption code for each target's workspace build — for review, not yet installable.

Usage ​

tsx
// @rozie-ui/text-field-react is not yet published to npm — this shows the workspace build, for review only.
import { useState } from 'react';
import { TextField } from '@rozie-ui/text-field-react';

export function Demo() {
  const [email, setEmail] = useState('');
  const error = email && !email.includes('@') ? 'Enter a valid email address.' : '';
  return (
    <TextField
      modelValue={email}
      onModelValueChange={setEmail}
      label="Email"
      type="email"
      description="We never share it."
      error={error}
      required
    />
  );
}

// A textarea that grows with its content, with a character counter.
export function NotesDemo() {
  const [notes, setNotes] = useState('');
  return (
    <TextField
      modelValue={notes}
      onModelValueChange={setNotes}
      label="Notes"
      multiline
      autosize
      maxRows={8}
      maxLength={500}
      showCount
    />
  );
}
vue
<!-- @rozie-ui/text-field-vue is not yet published to npm — this shows the workspace build, for review only. -->
<script setup lang="ts">
import { computed, ref } from 'vue';
import TextField from '@rozie-ui/text-field-vue';

const email = ref('');
const error = computed(() => (email.value && !email.value.includes('@') ? 'Enter a valid email address.' : ''));
const notes = ref('');
</script>

<template>
  <TextField v-model:modelValue="email" label="Email" type="email"
             description="We never share it." :error="error" required />

  <!-- A textarea that grows with its content, with a character counter -->
  <TextField v-model:modelValue="notes" label="Notes" multiline autosize
             :max-rows="8" :max-length="500" show-count />
</template>
svelte
<!-- @rozie-ui/text-field-svelte is not yet published to npm — this shows the workspace build, for review only. -->
<script lang="ts">
  import TextField from '@rozie-ui/text-field-svelte';

  let email = $state('');
  let notes = $state('');
  const error = $derived(email && !email.includes('@') ? 'Enter a valid email address.' : '');
</script>

<TextField bind:modelValue={email} label="Email" type="email"
           description="We never share it." {error} required />

<!-- A textarea that grows with its content, with a character counter -->
<TextField bind:modelValue={notes} label="Notes" multiline autosize maxRows={8} maxLength={500} showCount />
ts
// @rozie-ui/text-field-angular is not yet published to npm — this shows the workspace build, for review only.
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { TextField } from '@rozie-ui/text-field-angular';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [TextField, ReactiveFormsModule],
  template: `
    <!-- TextField is a ControlValueAccessor: bind it like any form control. -->
    <rozie-text-field [formControl]="email" label="Email" type="email" description="We never share it."
               [error]="email.touched && email.invalid ? 'Enter a valid email address.' : ''" [required]="true" />

    <!-- Or two-way bind the model directly. -->
    <rozie-text-field [(modelValue)]="notes" label="Notes" [multiline]="true" [autosize]="true" [maxRows]="8" />
  `,
})
export class DemoComponent {
  email = new FormControl('', [Validators.required, Validators.email]);
  notes = '';
}
tsx
// @rozie-ui/text-field-solid is not yet published to npm — this shows the workspace build, for review only.
import { createSignal } from 'solid-js';
import { TextField } from '@rozie-ui/text-field-solid';

export function Demo() {
  const [email, setEmail] = createSignal('');
  const error = () => (email() && !email().includes('@') ? 'Enter a valid email address.' : '');
  return (
    <TextField
      modelValue={email()}
      onModelValueChange={setEmail}
      label="Email"
      type="email"
      description="We never share it."
      error={error()}
      required
    />
  );
}
ts
// @rozie-ui/text-field-lit is not yet published to npm — this shows the workspace build, for review only.
import '@rozie-ui/text-field-lit';

// <rozie-text-field> is a custom element. Set props as properties (or kebab-case
// attributes), and listen for `model-value-change` to receive each keystroke as
// the two-way model, or `change` for a committed edit.
const el = document.querySelector('rozie-text-field');
el.label = 'Email';
el.type = 'email';
el.required = true;
el.addEventListener('model-value-change', (e) => {
  el.modelValue = e.detail;
  el.error = e.detail && !e.detail.includes('@') ? 'Enter a valid email address.' : '';
});

// The control and its label live in the element's shadow root, so they label
// each other there. A native <form> does not see the inner input: read
// `modelValue` when you submit.

Imperative handle ​

Beyond props and events, TextField exposes imperative methods (declared once in the .rozie source via $expose). Grab a handle through your framework's native ref mechanism and call them directly:

tsx
// @rozie-ui/text-field-react is not yet published to npm — this shows the workspace build, for review only.
import { useRef } from 'react';
import { TextField, type TextFieldHandle } from '@rozie-ui/text-field-react';

const field = useRef<TextFieldHandle>(null);
// <TextField ref={field} ... />
field.current?.focus();
field.current?.select();
field.current?.clear();
vue
<!-- @rozie-ui/text-field-vue is not yet published to npm — this shows the workspace build, for review only. -->
<script setup lang="ts">
import { ref } from 'vue';
const field = ref();          // template ref
</script>

<template>
  <TextField ref="field" v-model:modelValue="text" label="Search" />
  <button @click="field.clear()">Clear</button>
</template>
svelte
<!-- @rozie-ui/text-field-svelte is not yet published to npm — this shows the workspace build, for review only. -->
<script lang="ts">
  let field;                  // component instance via bind:this
</script>

<TextField bind:this={field} bind:modelValue={text} label="Search" />
<button onclick={() => field.clear()}>Clear</button>
ts
// @rozie-ui/text-field-angular is not yet published to npm — this shows the workspace build, for review only.
export class DemoComponent {
  @ViewChild(TextField) field!: TextField;   // or the viewChild() signal
  reset() { this.field.clear(); this.field.focus(); }
}
tsx
// @rozie-ui/text-field-solid is not yet published to npm — this shows the workspace build, for review only.
import { TextField, type TextFieldHandle } from '@rozie-ui/text-field-solid';

let handle: TextFieldHandle | undefined;

<TextField ref={(h) => (handle = h)} modelValue={text()} onModelValueChange={setText} label="Search" />;
handle?.clear();
ts
// @rozie-ui/text-field-lit is not yet published to npm — this shows the workspace build, for review only.
// The custom element IS the handle: exposed methods are public element methods.
const el = document.querySelector('rozie-text-field');
el.focus();   // moves focus into the inner control
el.select();
el.clear();

See also ​

Pre-1.0 — APIs may change between minor versions.