Hacktron Design System

Input

A single-line text field for typing a value.

<template>  <Input placeholder="Hello world" /></template>

Props

Name Type Default
size"md" | "sm""md"
errorbooleanfalse
monobooleanfalse
status"pending" | "valid" | "invalid"-
readonlybooleanfalse
...propsUInput props (type, placeholder, modelValue, disabled, icon, leadingIcon, trailingIcon, color, variant, ...)-

Slots

Name Description
leadingAn icon or a text prefix such as "app.hacktron.ai/". Measured, so the typed text is padded past it; clicks on it land on the field.
trailingA clear button or a suffix such as "/100". Measured, so the typed text stays clear of it; replaces the status icon.

Guidelines

Do

  • A placeholder is optional: the filled treatment reads the v-model value, not the browser's :placeholder-shown state.
  • Pair with UFormField for a labelled field (label, description, validation error) - Figma's "Input Labelled" frame is that pairing, not a separate component.
  • Inside a UFormField, a validation error puts the field on the error surface by itself. Use error for a state the form doesn't know, such as a failed lookup; it swaps to the actual error surface/border tokens, not just a color prop.
  • Use mono for a value that reads as code (a file path, a token, a URL slug); the field keeps its height.
  • Put a text prefix such as "app.hacktron.ai/" in the #leading slot; the typed text is padded past it automatically.
  • Use status for a lookup's progress (a slug availability check, a commit SHA): pending shows a spinner, valid a check, invalid a cross, in the trailing spot. Pair invalid with error.
  • A value shown for copying (a webhook URL, a generated secret) takes readonly: the field drops its hover change and text cursor (keyboard focus still shows the brand border, for select-and-copy), so it reads as a value, not a place to type. Put the copy button beside it or in #trailing.

Don't

  • Don't use type="file" expecting custom "Choose File" button styling beyond what UInput themes natively - Figma's "File" frame is the same Input component with type="file", not a bespoke file-picker.
  • Don't pass ui; it is rejected at type-check because it would replace this component's own styling. Mark an invalid field with error, not color.
  • Don't reach for color/variant to invent a new look - they're escape hatches to UInput's native palette for cases this token set doesn't cover, not a styling API for this component.

Examples

Leading and trailing icons

#trailing is a real forwarded slot (not just a static icon), so it can hold an interactive control like this clear button, shown only once there's text to clear. Input's own leading/trailing icon size is fixed at size-4 regardless of its own size prop, matching IconButton's scale - the same at both sizes here, no per-instance override needed.

<template>  <Input v-model="search" icon="i-lucide-search" placeholder="Search">    <template v-if="search" #trailing>      <IconButton icon="i-lucide-x" variant="plain" size="sm" aria-label="Clear search" @click="search = ''" />    </template>  </Input>    <Input v-model="searchSm" size="sm" icon="i-lucide-search" placeholder="Search">    <template v-if="searchSm" #trailing>      <IconButton icon="i-lucide-x" variant="plain" size="sm" aria-label="Clear search" @click="searchSm = ''" />    </template>  </Input></template>

Text prefix

A fixed prefix in #leading, e.g. the workspace URL before a slug. Input measures the slot and pads the typed text past it, so the prefix needs no per-site padding.

app.hacktron.ai/
<template>  <Input v-model="slug">    <template #leading>      <span class="text-text-tertiary">app.hacktron.ai/</span>    </template>  </Input></template>

Monospace value

mono switches the text to the mono-03 token for a value that reads as code (a file path, a token, a slug) while the field keeps the control height.

<template>  <Input v-model="affectedFile" mono /></template>

Validation status

status shows a lookup's progress in the trailing spot: a spinner while it runs, then a check or a cross. Pair invalid with error for the red surface.

<template>  <Input v-model="slug" status="pending" />  <Input v-model="slug" status="valid" />  <Input v-model="slug" status="invalid" error /></template>