Input
A native <input> with its label, hint and error message in one HTML contract, wired with for and aria-describedby. No JavaScript. Switch the brand: nothing in the component changes, only the semantic tokens behind it.
Size
Default: data-size="md", which also applies when the attribute is missing. There are no variants in v1.
smUsed only for the receipt.
mdUsed only for the receipt.
lgUsed only for the receipt.
States
Hover and focus-visible come from :hover and :focus-visible; disabled from the native disabled attribute; invalid from aria-invalid="true", together with an error message linked by aria-describedby. The invalid cells are also required: the * after the label is drawn from the native required attribute.
| Size | default | hover | focus-visible | disabled | invalid |
|---|---|---|---|---|---|
sm | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. Include an @ and a domain. |
md | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. Include an @ and a domain. |
lg | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. | Used only for the receipt. Include an @ and a domain. |
| Point at it | Tab to it | disabled | aria-invalid="true" |
Same height as Button
The md field and the md button use the same padding steps, border width and line height, so they line up in a form row. The layout (align-items, gap) belongs to the page, not to either component.
Reduced motion
Border, background and text colour change between states with motion.duration.state-change. That is feedback, not movement, so it stays under prefers-reduced-motion: reduce. Input has no other motion (ADR 0003).
Usage
<link rel="stylesheet" href="@iahola/troupe-tokens/troupe.css">
<link rel="stylesheet" href="@iahola/troupe-components/input/styles.css">
<div class="troupe-input" data-size="md">
<label class="troupe-input__label" for="email">Email address</label>
<input class="troupe-input__control" id="email" name="email" type="email" required
aria-invalid="true" aria-describedby="email-hint email-error">
<p class="troupe-input__hint" id="email-hint">Used only for the receipt.</p>
<p class="troupe-input__error" id="email-error">Include an @ and a domain.</p>
</div>Full specification: packages/components/input/README.md.