Skip to content
v1.4.2
Fit

Input

A single-line text field wrapped in a bordered container that supports an optional leading icon, a trailing node, and an invalid state. The wrapper handles focus, hover, and disabled styling so the inner <input> stays clean.

bash
jlds add input

Usage

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/input.css">

<div class="jl-input-wrap jl-input-wrap--md">
  <input class="jl-input" type="text" placeholder="[email protected]" />
</div>
vue
<script setup lang="ts">
import { ref } from "vue"
import { Input } from "@/components/ui/input"

const email = ref("")
</script>

<template>
  <Input v-model="email" placeholder="[email protected]" />
</template>
tsx
import { Input } from "@/components/ui/input"

<Input placeholder="[email protected]" />

Sizes

sm (32px) · md (38px, default) · lg (44px)

html
<div class="jl-input-wrap jl-input-wrap--sm"><input class="jl-input" placeholder="Small" /></div>
<div class="jl-input-wrap jl-input-wrap--md"><input class="jl-input" placeholder="Medium" /></div>
<div class="jl-input-wrap jl-input-wrap--lg"><input class="jl-input" placeholder="Large" /></div>
vue
<template>
  <Input size="sm" placeholder="Small" />
  <Input size="md" placeholder="Medium" />
  <Input size="lg" placeholder="Large" />
</template>
tsx
<Input size="sm" placeholder="Small" />
<Input size="md" placeholder="Medium" />
<Input size="lg" placeholder="Large" />

Icon & trailing

Add a leading icon or a trailing node (icon, button, or hint). In HTML, wrap each in a jl-input-adorn span on the correct side of the <input>.

html
<div class="jl-input-wrap jl-input-wrap--md">
  <span class="jl-input-adorn">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
    </svg>
  </span>
  <input class="jl-input" placeholder="Search" />
</div>

<div class="jl-input-wrap jl-input-wrap--md">
  <input class="jl-input" placeholder="Amount" />
  <span class="jl-input-adorn">USD</span>
</div>
vue
<template>
  <Input placeholder="Search">
    <template #icon><SearchIcon /></template>
  </Input>
  <Input placeholder="Amount">
    <template #trailing>USD</template>
  </Input>
</template>
tsx
import { Search } from "lucide-react"

<Input icon={<Search />} placeholder="Search" />
<Input trailing="USD" placeholder="Amount" />

Invalid & disabled

Add invalid for error styling (red border + danger focus ring) or disabled.

html
<div class="jl-input-wrap jl-input-wrap--md" data-invalid="true">
  <input class="jl-input" value="not-an-email" aria-invalid="true" />
</div>
<div class="jl-input-wrap jl-input-wrap--md" data-disabled="true">
  <input class="jl-input" value="Disabled" disabled />
</div>
vue
<template>
  <Input invalid model-value="not-an-email" />
  <Input disabled model-value="Disabled" />
</template>
tsx
<Input invalid defaultValue="not-an-email" />
<Input disabled defaultValue="Disabled" />

Clearable & affixes

clearable shows a × that resets a controlled value; prefix / suffix add inline text addons (URLs, units) — distinct from the icon / trailing node slots.

html
<div class="jl-input-wrap jl-input-wrap--md">
  <span class="jl-input-affix">jlds.dev/</span>
  <input class="jl-input" value="acme" />
</div>
<div class="jl-input-wrap jl-input-wrap--md">
  <input class="jl-input" value="40" />
  <span class="jl-input-affix">ms</span>
</div>
<!-- clearable: add a .jl-input-clear button (toggle it yourself in vanilla) -->
vue
<template>
  <Input v-model="q" clearable :icon="…" placeholder="Search projects" />
  <Input v-model="slug" prefix="jlds.dev/" />
  <Input v-model="ttl" suffix="ms" />
</template>
tsx
<Input clearable value={q} onChange={(e) => setQ(e.target.value)} icon={<SearchIcon />} placeholder="Search projects" />
<Input prefix="jlds.dev/" value={slug} onChange={…} />
<Input suffix="ms" value={ttl} onChange={…} />

Props

React

Input extends React.InputHTMLAttributes<HTMLInputElement> (minus the native size), so type, placeholder, value, onChange, etc. all pass through.

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Control height and font size
iconReact.ReactNodeLeading icon node
trailingReact.ReactNodeTrailing node (icon, button, hint)
prefixReact.ReactNodeInline text/node before the field
suffixReact.ReactNodeInline text/node after the field
clearablebooleanfalseShow a × to clear a non-empty controlled value
onClear(e) => voidClear handler (defaults to emitting an empty-value change)
invalidbooleanfalseShow error styling

Vue

Input supports v-model; other attributes (type, placeholder, …) fall through to the inner <input>.

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Control height and font size
prefixstringInline text before the field (or prefix slot)
suffixstringInline text after the field (or suffix slot)
clearablebooleanfalseShow a × to clear; emits clear and sets value to ""
invalidbooleanfalseShow error styling
disabledbooleanfalseDisable the field
modelValuestring | numberBound value (v-model)

Slots: icon (leading), trailing (trailing), prefix, suffix.

CSS classes (HTML)

ClassPurpose
.jl-input-wrapBordered wrapper — handles focus/hover/disabled — always required
.jl-input-wrap--sm / --md / --lgSize
.jl-inputThe inner <input> element
.jl-input-adornLeading/trailing icon or text slot
.jl-input-affixInline prefix/suffix text addon
.jl-input-clearClear (×) button
[data-invalid="true"]Error styling (on the wrapper)
[data-disabled="true"]Disabled styling (on the wrapper)