Input
Atombeta@macaw-ds/ui · since 0.1.0Single-line text entry: names, emails, references, amounts. Not for multi-line text, dates or selections.
Import
import { Input, inputVariants } from '@macaw-ds/ui';Props
| Prop | Type | Default | Description |
|---|---|---|---|
| type | "text" | "email" | "password" | "search" | "url" | "tel" | "number" | text | HTML input type. number is unformatted; for currency, format in the consumer and keep type="text" with inputMode="decimal". |
| size | "sm" | "md" | "lg" | md | 28, 32 or 40 px tall, matching Button sizes so they line up in a row. Accessibility: All sizes meet the 24 px minimum target size. |
| align | "start" | "end" | start | end switches on tabular numerals and right-aligns. Use for amounts. |
| invalid | boolean | false | Shows the critical border and sets aria-invalid. Always pair with a visible message linked via aria-describedby. Accessibility: aria-invalid=true is announced; the message must be in the DOM and linked. |
| disabled | boolean | false | Not editable and not focusable. Accessibility: Disabled fields leave the tab order. Prefer readOnly when the value must still be readable. |
| readOnly | boolean | false | Readable and focusable, not editable. Sunken background. Accessibility: Read-only fields stay in the tab order and are announced as read-only. |
| placeholder | string | 0.00 | Example value, not a label. Meets 4.5:1 but disappears on typing. Accessibility: Never the only label. |
| startAdornment | ReactNode | — | Decorative prefix: an Icon or a currency symbol. |
| endAdornment | ReactNode | — | Decorative suffix: a unit such as USD or %. |
| id | string | — | Required when a visible label uses htmlFor. |
Slots
Named parts you pass content into.
- startAdornment
- endAdornment
Tokens used
Every value this component renders comes from one of these tokens.
| Token | Light | Dark |
|---|---|---|
| input.radius component · dimension | 4px | 4px |
| input.height.sm component · dimension | 28px | 28px |
| input.height.md component · dimension | 32px | 32px |
| input.height.lg component · dimension | 40px | 40px |
| input.bg.default component · color | oklch(1 0 0) | oklch(0.11 0.01 250) |
| input.bg.disabled component · color | oklch(0.965 0.003 250) | oklch(0.21 0.012 250) |
| input.line.default component · color | oklch(0.66 0.01 250) | oklch(0.48 0.012 250) |
| input.line.hover component · color | oklch(0.53 0.012 250) | oklch(0.53 0.012 250) |
| input.line.invalid component · color | oklch(0.53 0.2 27) | oklch(0.6 0.21 27) |
| input.line.disabled component · color | oklch(0.87 0.007 250) | oklch(0.29 0.012 250) |
| input.fg.default component · color | oklch(0.15 0.012 250) | oklch(0.985 0.002 250) |
| input.fg.placeholder component · color | oklch(0.53 0.012 250) | oklch(0.66 0.01 250) |
| input.fg.disabled component · color | oklch(0.66 0.01 250) | oklch(0.48 0.012 250) |
| color.focus.ring semantic · color | oklch(0.51 0.2 262) | oklch(0.68 0.16 262) |
| color.surface.layer-2 semantic · color | oklch(0.985 0.002 250) | oklch(0.21 0.012 250) |
| color.fg.muted semantic · color | oklch(0.48 0.012 250) | oklch(0.66 0.01 250) |
Eject
Ejecting copies the source into your repo and records it in .macaw/ejected.json, so the fork stays visible. The same file is served as a shadcn-compatible registry item.
npx @macaw-ds/cli add input --ejectnpx shadcn@latest add https://macaw-design-system.vercel.app/r/input.jsonBundle budget: 2,500 bytes gzipped, enforced by size-limit in CI.
Manifest
registry/manifests/input.json, the contract the playground, CLI, plugin and MCP server read.
{
"name": "Input",
"slug": "input",
"package": "@macaw-ds/ui",
"status": "beta",
"since": "0.1.0",
"level": "atom",
"category": "form",
"purpose": "Single-line text entry: names, emails, references, amounts. Not for multi-line text, dates or selections.",
"description": "The text field primitive. Labelled by contract (TypeScript requires an id for a visible label, or aria-label / aria-labelledby), with adornments for currency symbols and units, an `invalid` state wired to aria-invalid, and `align=\"end\"` for money. Formatting of money and numbers belongs to the consumer until the NumberField ships.",
"exports": [
"Input",
"inputVariants"
],
"props": {
"type": {
"type": "enum",
"values": [
"text",
"email",
"password",
"search",
"url",
"tel",
"number"
],
"default": "text",
"required": false,
"description": "HTML input type. number is unformatted; for currency, format in the consumer and keep type=\"text\" with inputMode=\"decimal\".",
"control": "select"
},
"size": {
"type": "enum",
"values": [
"sm",
"md",
"lg"
],
"default": "md",
"required": false,
"description": "28, 32 or 40 px tall, matching Button sizes so they line up in a row.",
"a11y": "All sizes meet the 24 px minimum target size."
},
"align": {
"type": "enum",
"values": [
"start",
"end"
],
"default": "start",
"required": false,
"description": "end switches on tabular numerals and right-aligns. Use for amounts."
},
"invalid": {
"type": "boolean",
"default": false,
"required": false,
"description": "Shows the critical border and sets aria-invalid. Always pair with a visible message linked via aria-describedby.",
"a11y": "aria-invalid=true is announced; the message must be in the DOM and linked."
},
"disabled": {
"type": "boolean",
"default": false,
"required": false,
"description": "Not editable and not focusable.",
"a11y": "Disabled fields leave the tab order. Prefer readOnly when the value must still be readable."
},
"readOnly": {
"type": "boolean",
"default": false,
"required": false,
"description": "Readable and focusable, not editable. Sunken background.",
"a11y": "Read-only fields stay in the tab order and are announced as read-only."
},
"placeholder": {
"type": "string",
"default": "0.00",
"required": false,
"description": "Example value, not a label. Meets 4.5:1 but disappears on typing.",
"a11y": "Never the only label."
},
"startAdornment": {
"type": "node",
"required": false,
"description": "Decorative prefix: an Icon or a currency symbol.",
"control": "none"
},
"endAdornment": {
"type": "node",
"required": false,
"description": "Decorative suffix: a unit such as USD or %.",
"control": "none"
},
"id": {
"type": "string",
"required": false,
"description": "Required when a visible label uses htmlFor.",
"control": "none"
}
},
"slots": [
"startAdornment",
"endAdornment"
],
"tokens": [
"input.radius",
"input.height.sm",
"input.height.md",
"input.height.lg",
"input.bg.default",
"input.bg.disabled",
"input.line.default",
"input.line.hover",
"input.line.invalid",
"input.line.disabled",
"input.fg.default",
"input.fg.placeholder",
"input.fg.disabled",
"color.focus.ring",
"color.surface.layer-2",
"color.fg.muted"
],
"a11y": {
"role": "textbox (searchbox for type=\"search\")",
"focusable": true,
"keyboard": [
"Tab: focus",
"Typing edits the value",
"Escape clears a type=\"search\" field in most browsers"
],
"name": "From a visible label via id + htmlFor (preferred), or aria-label / aria-labelledby. TypeScript requires one of the three.",
"notes": [
"Border meets 3:1 non-text contrast against the field background in both themes; the invalid border is the critical colour.",
"Placeholder text meets 4.5:1 but is never the label.",
"Adornments are aria-hidden. If the unit matters, say it in the label: \"Amount (USD)\".",
"Error messages are linked with aria-describedby so they are announced with the field."
]
},
"motion": {
"description": "Border colour eases on hover.",
"tokens": [
"duration.fast",
"ease.standard"
]
},
"budget": {
"gzip": 2500
},
"rules": [
"Every Input has a visible label (Text as=\"label\" htmlFor) unless it is a search box with aria-label.",
"Amounts use align=\"end\" and inputMode=\"decimal\"; the consumer formats the value.",
"invalid is always accompanied by a message linked via aria-describedby.",
"Use readOnly, not disabled, for values the user must still be able to read and copy."
],
"dos": [
"Put the currency symbol in startAdornment and the code in endAdornment: $ … USD.",
"Match Input size to the Button beside it."
],
"donts": [
"Do not use placeholder as the label.",
"Do not use type=\"number\" for money; it strips formatting and spinners misfire."
],
"examples": [
{
"name": "Default",
"description": "With a visible label.",
"props": {
"id": "ref",
"placeholder": "INV-0041"
}
},
{
"name": "Amount",
"props": {
"id": "amount",
"align": "end",
"placeholder": "0.00",
"inputMode": "decimal"
}
},
{
"name": "Invalid",
"props": {
"id": "email",
"type": "email",
"invalid": true,
"defaultValue": "not-an-email"
}
},
{
"name": "Disabled",
"props": {
"id": "d",
"disabled": true,
"defaultValue": "Locked"
}
},
{
"name": "Read only",
"props": {
"id": "r",
"readOnly": true,
"defaultValue": "acct_1Nv2hZ"
}
}
],
"registryDependencies": [],
"dependencies": [
"class-variance-authority"
],
"related": [
"text",
"button"
]
}