Skip to content

Input

A native input that joins a Field on its own, with the ID, the ARIA wiring and the states already right.

View source View as Markdown

Input renders a real <input> and joins the surrounding Field without being asked. Use it for a single line of text; for anything longer, use Textarea.

We only use this to send receipts.

App.vue
<template>
  <div class="flex w-[17rem] flex-col gap-6">
    <Field.Root
      class="group flex flex-col gap-1.5"
      :options="{ name: 'email', validate: validateEmail }"
    >
      <div :class="box">
        <div :class="stack">
          <span :class="line">
            <Input
              v-model="email"
              :class="control"
              type="email"
              placeholder=" "
              autocomplete="off"
            />
          </span>
          <Field.Label :class="label">Email</Field.Label>
        </div>
      </div>

      <Field.Description :class="hint">
        We only use this to send receipts.
      </Field.Description>

      <Field.Error v-slot="{ messages }" :class="error">
        {{ messages.join(' ') }}
      </Field.Error>
    </Field.Root>

    <Field.Root
      class="group flex flex-col gap-1.5"
      :options="{ name: 'account', disabled: true }"
    >
      <div :class="box">
        <div :class="stack">
          <span :class="line">
            <Input
              :class="control"
              default-value="[email protected]"
              placeholder=" "
            />
          </span>
          <Field.Label :class="label">Account</Field.Label>
        </div>
      </div>
    </Field.Root>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Field } from '@maas/mirror/vue/Field'
import { Input } from '@maas/mirror/vue/Input'

const email = ref('')

const box = [
  'relative isolate flex h-12 w-full cursor-text items-center justify-between gap-1.5',
  'rounded-component-lg border-surface border-2 px-[0.875rem]',
  'transition-all duration-100 ease-linear [&_*]:transition-all [&_*]:duration-100 [&_*]:ease-linear',
  'outline-4 outline-transparent focus-within:focus-ring',
  'group-[[data-invalid=true]:not([data-focused=true])]:border-danger-subtle',
  'group-[[data-invalid=true]:not([data-focused=true])]:bg-danger-subtle',
  'group-data-[disabled=true]:border-disabled-subtle group-data-[disabled=true]:cursor-not-allowed',
].join(' ')

const stack = [
  'group/stack flex h-full max-h-full w-full flex-col-reverse',
  'items-center justify-center gap-0 px-1',
  'focus-within:gap-1 has-[input:not(:placeholder-shown)]:gap-1',
].join(' ')

const line = [
  'relative flex h-0 w-full items-end',
  'group-focus-within/stack:h-[0.9375rem]',
  'has-[input:not(:placeholder-shown)]:h-[0.9375rem]',
].join(' ')

const control = [
  'type-component-lg leading-[normal]! text-surface',
  'block box-content h-[1lh] w-full py-1.25 -my-1.25 appearance-none bg-transparent outline-none',
  'placeholder:text-transparent',
  'group-[[data-invalid=true]:not([data-focused=true])]:text-danger-on-muted',
  'data-[disabled=true]:text-disabled-solid',
].join(' ')

const label = [
  'type-component-lg leading-[normal]! text-surface-muted',
  'flex h-full w-full items-center [--mirror-label-cursor:text]',
  "before:absolute before:-inset-0.5 before:-z-10 before:content-['']",
  'group-focus-within/stack:h-auto group-focus-within/stack:text-[0.6875rem] group-focus-within/stack:[--mirror-label-cursor:auto]',
  'group-has-[input:not(:placeholder-shown)]/stack:h-auto group-has-[input:not(:placeholder-shown)]/stack:text-[0.6875rem] group-has-[input:not(:placeholder-shown)]/stack:[--mirror-label-cursor:auto]',
  'group-[[data-invalid=true]:not([data-focused=true])]:text-danger-muted',
  'group-data-[disabled=true]:text-disabled-muted',
].join(' ')

const hint = 'type-component-2xs text-surface-muted'
const error = 'type-component-2xs text-danger-muted'

function validateEmail(value: unknown) {
  const entry = String(value ?? '')

  if (entry.length === 0) {
    return 'Enter an email address.'
  }

  return entry.includes('@') ? null : 'Enter a valid email address.'
}
</script>

Usage guidelines

  • Set id on Field.Root rather than on the input, because Label points its for at the field’s control ID and an id here leaves it pointing at nothing.must
  • Only one control may register per field, so two inputs need two fields; a second one throws duplicate_field_control.must