Components · Data Entry
Input
A single-line text field — names, search, numbers, short free text — with sizes, error/warning status, affixes, a clear ✕ and a password reveal.
Examples
Size
Status
Prefix
Clearable
States · error · rest · warning · disabled · readonly
Sizes · small 24 · default 32 · large 40
Affixes · prefix icon · suffix text · clearable · password reveal
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-input> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css.
Icons are summoned by name from the DS icon library via <aha-icon>. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // registers <aha-icon> (affix glyphs)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-input.js'; // registers <aha-input>
</script>
<!-- size: small | default | large · status: error | warning · disabled / readonly -->
<aha-input id="ws" size="large" placeholder="Workspace name" value="AhaSlides"></aha-input>
<!-- affixes: a leading prefix icon, a trailing suffix text, a clear ✕, a password reveal -->
<aha-input prefix-icon="system-magnifying-glass" type="search" clearable placeholder="Search slides"></aha-input>
<aha-input suffix=".ahaslides.com" value="myteam"></aha-input>
<aha-input type="password" prefix-icon="system-lock" value="secret123"></aha-input>
<aha-input status="error" value="not-an-email" placeholder="Email"></aha-input>
<script>
// Plain DOM — no framework. Read e.detail.value off the composed CustomEvent.
document.getElementById('ws').addEventListener('input', (e) => {
console.log('value:', e.detail.value);
});
</script>
import '@ahaslides-product/design/icons'; // registers <aha-icon> (affix glyphs)
import '@ahaslides-product/design/aha-input'; // registers <aha-input>
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper; React 19 can use <aha-input> directly.
// size: 'small' | 'default' | 'large' · status: 'error' | 'warning'
function AhaInput({ value, placeholder, type, size, status, disabled, readOnly,
prefixIcon, suffix, clearable, onChange }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
el.value = value ?? '';
el.disabled = !!disabled;
const h = (e) => onChange?.(e.detail.value); // composed CustomEvent
el.addEventListener('input', h);
return () => el.removeEventListener('input', h);
});
return (
<aha-input
ref={ref}
placeholder={placeholder}
type={type}
{...(size ? { size } : {})}
{...(status ? { status } : {})}
{...(readOnly ? { readonly: '' } : {})}
{...(prefixIcon ? { 'prefix-icon': prefixIcon } : {})}
{...(suffix ? { suffix } : {})}
{...(clearable ? { clearable: '' } : {})}
/>
);
}
// usage
<AhaInput value={email} placeholder="Email" status="error"
prefixIcon="system-envelope" clearable onChange={setEmail} />
// main.ts — register the elements + mark aha-* as custom elements
import '@ahaslides-product/design/icons'; // registers <aha-icon> (affix glyphs)
import '@ahaslides-product/design/aha-input'; // registers <aha-input>
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const email = ref('');
</script>
<template>
<!-- size: small | default | large · status: error | warning · prefix-icon / suffix / clearable -->
<aha-input
size="large"
status="error"
prefix-icon="system-envelope"
clearable
placeholder="Email"
:value.prop="email"
@input="email = $event.detail.value"
@clear="email = ''"
/>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
value | string | "" | Controlled text value |
placeholder | string | — | Hint shown when empty |
type | text | number | email | password | search | text | Native input type; password adds a reveal (eye) toggle |
size | small | default | large | default | Control height — small 24 · default 32 · large 40 (DS V3 field scale); radius stays 8 (sm/md/lg accepted as aliases) |
status | default | error | warning | default | Border + ring recolour; error also sets aria-invalid (legacy `invalid` boolean = status error) |
disabled | boolean | false | Non-interactive; grey fill |
readonly | boolean | false | Selectable but not editable; secondary fill |
prefix-icon / prefix | string | — | Leading affix — an icon by name (from the DS library) or plain text |
suffix-icon / suffix | string | — | Trailing affix — an icon by name or plain text (e.g. a domain) |
clearable | boolean | false | Shows a ✕ when non-empty that wipes the value and emits `clear` |
input | CustomEvent<{value}> | — | Composed event on each keystroke; read e.detail.value |
clear | CustomEvent<{value}> | — | Composed event when the ✕ clears the field |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-input'; // registers <aha-input>
Agent feed for this component (absolute, fetchable anywhere): input.agent.json · input.md · input.llms.txt
When to use
When to use
- Input — short single-line free text — a name, a search term, a number
- Textarea — multi-line free text (descriptions, messages)
- Select — one value from a known set — don't make the user type it
Label the field with a noun phrase; use placeholder for an example, never as the label. Show the error status only after the user leaves the field, and pair it with a message that says how to fix it. Reach for a prefix icon to hint the field's job (a magnifier for search, an envelope for email), a suffix for a fixed unit or domain, clearable for search/filter fields, and password reveal for credentials.
Surfaces
editor dashboard settings audience
Spec
Box height 32 (default) · radius 8 · padding 0 12 · Sizes small 24 · default 32 · large 40 — all radius 8 · Rest border 1px #E3E3E3 · Hover border #D3B4FF (purple-30) · Focus #6A1EBB border + 2px #D3B4FF@30% ring (on the persistent wrapper) · Error #F5222D border + error-tinted ring; sets aria-invalid · Warning #FF7747 border + warning-tinted ring · Disabled #F1F1F1 fill, #EBEBEB border, not-allowed · Readonly #F7F7F7 fill, still selectable · Affixes prefix/suffix icon (by name) or text; clearable ✕; password reveal (eye) · Text Plus Jakarta 14/21; placeholder #8A8A8A