Tessera UI

Components

TextField

A text input with a visible label and an optional hint. The label is required: it is what assistive technology reads.

The preview needs JavaScript. The code is in the Code tab.

TextField-0.tess
<template>
  <div>
    <TextField>
      <span slot="label">Email</span>
      <span slot="hint">We only use it to sign you in.</span>
    </TextField>
    <TextField error="Enter your name.">
      <span slot="label">Name</span>
    </TextField>
  </div>
</template>

Installation

npx @pixelscriptio/tessera add TextField
pnpm exec tessera add TextField
yarn tessera add TextField
bunx @pixelscriptio/tessera add TextField
  • Works with Tessera 0.9.0 or newer.
  1. Copy and paste the code into your project

    Put it in system/components.

    system/components/TextField.tess
    <contract>
    {
      "name": "TextField",
      "role": "generic",
      "description": "A text input with a visible label and an optional hint. The label is required: it is what assistive technology reads.",
      "since": "0.5.0",
      "parts": ["root", "label", "input", "hint", "error"],
      "variants": {
        "size": ["md", "sm"] // first value is the default
      },
      "states": ["hover", "focus-visible", "disabled"],
      "slots": {
        "label": "the visible label, required",
        "hint": "help under the input"
      },
      "events": {
        "input": "fired with the new text on every change",
        "change": "fired with the text when the person leaves the field after changing it"
      },
      "preview": {
        "slots": {
          "label": "Email",
          "hint": "We only use it to sign you in."
        }
      }
    }
    </contract>
    
    <script>
    const { value, type, placeholder, disabled, invalid, error } = props({
      value: '',
      type: 'text',
      placeholder: '',
      disabled: false,
      invalid: false,
      error: '',
    })
    
    // Ids of its own, so the hint and the error can describe the input however many fields there are.
    const uid = `t-textfield-${Math.random().toString(36).slice(2, 9)}`
    
    // The field owns what is typed, starts from the prop and follows it when it changes.
    const text = state(value.value)
    effect(() => {
      text.value = value.value
    })
    
    function typed(event) {
      emit('input', event.target.value)
    }
    
    function committed(event) {
      emit('change', event.target.value)
    }
    </script>
    
    <template>
      <label class="root">
        <span class="label"><slot name="label" /></span>
        <input
          class="input"
          :type="type"
          :placeholder="placeholder"
          :disabled="disabled"
          :aria-invalid="invalid || error ? 'true' : null"
          :aria-describedby="uid + '-hint ' + uid + '-error'"
          bind:value="text"
          @input="typed($event)"
          @change="committed($event)"
          />
        <span class="hint" :id="uid + '-hint'"><slot name="hint" /></span>
        <span class="error" :id="uid + '-error'" aria-live="polite">{error}</span>
      </label>
    </template>
    
    <style>
    .root {
      display: flex;
      flex-direction: column;
      gap: $space.1;
      color: $color.text.default;
      font-family: var(--t-font-family-sans);
      font-size: $font.size.sm;
    }
    
    .label {
      font-size: $font.size.sm;
      font-weight: $font.weight.bold;
    }
    
    .input {
      padding: $space.2 $space.3;
      border: 1px solid $color.border.strong;
      border-radius: $radius.md;
      background: $color.surface.default;
      color: $color.text.default;
      font: inherit;
      transition-duration: $motion.duration.fast;
    
      &:hover:not(:disabled) {
        border-color: $color.text.default;
      }
    
      &:focus-visible {
        outline: 2px solid $color.focus.ring;
        outline-offset: 2px;
      }
    
      &:disabled {
        opacity: $opacity.disabled;
        cursor: not-allowed;
      }
    
      &[aria-invalid='true'] {
        border-color: $color.action.danger.text;
      }
    }
    
    .root[data-size='sm'] .input {
      padding: $space.1 $space.2;
      font-size: $font.size.sm;
    }
    
    .hint {
      color: $color.text.muted;
      font-size: $font.size.sm;
    }
    
    .error {
      color: $color.action.danger.text;
      font-size: $font.size.sm;
    }
    
    .hint:empty,
    .error:empty {
      display: none;
    }
    </style>
    
  2. Check your design tokens

    Your design system needs these. tessera add reports the ones that are missing; by hand, run npx @pixelscriptio/tessera check.

    color.action.danger.text color.border.strong color.focus.ring color.surface.default color.text.default color.text.muted font.size.sm font.weight.bold motion.duration.fast opacity.disabled radius.md space.1 space.2 space.3

Usage

Using TextField

A text input with a visible label and an optional hint.

  • The label is required. It is what assistive technology reads. Do not use the placeholder as the label.
  • Use the hint slot for help, and the error prop for what is wrong. The error is announced when it appears, is read with the field, and turns the border red. Setting error also marks the field invalid. Set invalid alone when the reason is shown elsewhere.
  • type is any text-like input type: text, email, password, search, url, tel.
  • input fires on every change with the new text, and change fires when the person leaves the field.

The preview needs JavaScript. The code is in the Code tab.

TextField-0.tess
<template>
  <div>
    <TextField>
      <span slot="label">Email</span>
      <span slot="hint">We only use it to sign you in.</span>
    </TextField>
    <TextField error="Enter your name.">
      <span slot="label">Name</span>
    </TextField>
  </div>
</template>

API reference

Variants

VariantValues
sizemd default sm

Slots

SlotContent
labelthe visible label, required
hinthelp under the input

Events

EventWhen
inputfired with the new text on every change
changefired with the text when the person leaves the field after changing it

Anatomy

Rolegeneric
Partsroot label input hint error
Stateshover focus-visible disabled
Since0.5.0

Design tokens it uses

Your design system needs these. tessera add checks that it has them, and can add the ones it lacks.

color.action.danger.text color.border.strong color.focus.ring color.surface.default color.text.default color.text.muted font.size.sm font.weight.bold motion.duration.fast opacity.disabled radius.md space.1 space.2 space.3

Checksum

cc179afa8c4c5ae16fe908c6937edf5dad05fc5f2c9704f183a7af62417e41e4