Skip to content

Inputs

TextField

Styled text input with label, helper text, error state and leading and trailing slots.

<script>
  import { TextField } from "@codeatlasdev/ui";
  let email = $state("");
</script>

<TextField label="Email" type="email" bind:value={email} />

An error replaces the description and is announced to assistive tech. On touch devices the input text stays at 16px or more so iOS Safari does not zoom on focus. Native input attributes (maxlength, inputmode, data-*, onkeydown, onfocus…) are spread on the <input>, and bind:ref gives you the element. For an inline field whose label is implied visually, set hideLabel (the label stays for screen readers) or pass aria-label without label. Wrapper components such as MaskedField, MoneyInput and PhoneField forward these props to TextField.

#056

Default

Basic text inputs with a label.

Reference #056 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { TextField } from '@codeatlasdev/ui';

  let name = $state('');
  let email = $state('');
</script>

<div class="col">
  <TextField label="Full name" placeholder="Jane Smith" bind:value={name} />
  <TextField label="Email" placeholder="you@example.com" type="email" bind:value={email} />
</div>

<style>
  .col {
    display: flex;
    flex-direction: column;
    gap: var(--ca-space-4);
    width: 100%;
    max-width: 400px;
  }
</style>
#057

With icons

leading and trailing snippets. Interactive trailing content, like a reveal button, must be a real button with its own label.

Reference #057 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { TextField } from '@codeatlasdev/ui';
  import { Eye, EyeOff, Search } from '@lucide/svelte';

  let reveal = $state(false);
  let password = $state('');
</script>

<div class="col">
  <TextField label="Search" placeholder="Type to search…">
    {#snippet leading()}<Search size={16} stroke-width={2} />{/snippet}
  </TextField>
  <TextField label="Password" type={reveal ? 'text' : 'password'} placeholder="••••••••" bind:value={password}>
    {#snippet trailing()}
      <button type="button" class="reveal" aria-label={reveal ? 'Hide password' : 'Show password'} aria-pressed={reveal} onclick={() => (reveal = !reveal)}>
        {#if reveal}<EyeOff size={16} stroke-width={2} />{:else}<Eye size={16} stroke-width={2} />{/if}
      </button>
    {/snippet}
  </TextField>
</div>

<style>
  .col {
    display: flex;
    flex-direction: column;
    gap: var(--ca-space-4);
    width: 100%;
    max-width: 400px;
  }

  .reveal {
    min-width: var(--ca-touch-target);
    min-height: var(--ca-touch-target);
    margin-block: calc((var(--ca-touch-target) - 1lh) / -2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    display: inline-flex;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
  }
</style>
#058

States

Helper text, error, required, read-only, disabled and the small size.

3–20 characters, letters and underscores only.

Reference #058 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { TextField } from '@codeatlasdev/ui';

  let wrong = $state('wrong@');
</script>

<div class="col">
  <TextField label="Username" placeholder="john_doe" description="3–20 characters, letters and underscores only." />
  <TextField label="Email" bind:value={wrong} error="Please enter a valid email address." />
  <TextField label="Company" required />
  <TextField label="Read-only field" value="Cannot edit this" readonly />
  <TextField label="Disabled field" value="Cannot edit this" disabled />
  <TextField label="Small" size="sm" placeholder="Compact height" />
</div>

<style>
  .col {
    display: flex;
    flex-direction: column;
    gap: var(--ca-space-4);
    width: 100%;
    max-width: 400px;
  }
</style>
#389

Hidden label

hideLabel keeps the label for assistive tech only; maxlength and ref reach the native input.

Reference #389 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { Button, TextField } from '@codeatlasdev/ui';

  let items = $state([{ name: 'Design review' }, { name: 'Ship 4.3' }]);
  let first = $state<HTMLInputElement | null>(null);
</script>

<div class="col">
  {#each items as item, i (i)}
    <TextField
      label="Task name"
      hideLabel
      bind:value={item.name}
      bind:ref={first}
      maxlength={40}
      autocomplete="off"
    />
  {/each}
  <Button size="sm" variant="text" onclick={() => first?.focus()}>Focus the last field</Button>
</div>

<style>
  .col {
    display: flex;
    flex-direction: column;
    gap: var(--ca-space-3);
    width: 100%;
    max-width: 400px;
  }
</style>

Props

PropTypeDefaultDescription
labelstring–Label text.
hideLabelbooleanfalseRenders the label with `.ca-sr-only`: read by screen readers, not shown.
placeholderstring''Input placeholder.
valuestring''Bindable input value.
typestring'text'HTML input type.
errorstring–Validation error message. Puts the field in the error state.
descriptionstring–Non-error hint shown below the field.
helperTextstring–Deprecated alias of description.
disabledbooleanfalseDisables the input.
requiredbooleanfalseMarks the field as required.
readonlybooleanfalseMakes the input read-only.
autocompleteHTMLInputAttributes['autocomplete']–Native autocomplete hint.
namestring–Native name, for form submission.
size'sm' | 'md''md'Field height.
leadingSnippet–Icon or element before the input.
trailingSnippet–Icon or element after the input.
skeletonbooleanfalseLoading placeholder with the exact size of the loaded field (disabled, `aria-busy`). See Skeleton.
classstring–Extra classes on the root.
refHTMLInputElement | null–Bindable native input element.
oninput(value: string) => void–Called on every keystroke with the new value.
onchange(value: string) => void–Called when the value is committed (blur or Enter).
onfocus(e: FocusEvent) => void–Called when the input gains focus.
onblur(e: FocusEvent) => void–Called when the input loses focus.