diff --git a/.specs/helper-text.md b/.specs/helper-text.md
new file mode 100644
index 00000000..913a423d
--- /dev/null
+++ b/.specs/helper-text.md
@@ -0,0 +1,113 @@
+---
+name: helper-text
+category: inputs
+structure: monolithic
+status: implemented
+spec_version: 1
+figma:
+ url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=600-5603
+ node_id: 600:5603
+checksum: 60c3e601c3c8980940290cb81cb9499e2e2be12d7bb702fa96547257b0a2541e
+created: 2026-06-15
+last_updated: 2026-06-15
+---
+
+# HelperText — Component Spec
+
+## Purpose
+
+Auxiliary text rendered below a form input to communicate guidance (`helper`), validation errors (`invalid`), required-field reminders (`required`), or a locked/disabled state (`disabled`). Each variant changes only color (and, for `disabled`, prepends a lock icon) so the visual weight stays consistent with the field above it.
+
+## Usage
+
+```vue
+
+
+
+
+
+
+```
+
+## Props
+
+| Prop | Type | Default | Required | JSDoc |
+|---|---|---|---|---|
+| `value` | `string` | `''` | no | Fallback text when the default slot is empty. |
+| `kind` | `'helper' \| 'invalid' \| 'required' \| 'disabled'` | `'helper'` | no | Visual variant; `disabled` also prepends a `pi pi-lock` icon. |
+
+## Events
+
+| _none_ | — | — |
+
+## Slots
+
+| Slot | Scope | Notes |
+|---|---|---|
+| `default` | — | Helper text; falls back to `value` prop when empty. |
+
+## States
+
+- Visual states: `helper`, `invalid`, `required`, `disabled`
+- `data-kind` mirrors the `kind` prop
+
+## Motion & Animations
+
+_none_
+
+## Tokens
+
+| Region | Token (DESIGN.md) |
+|---|---|
+| typography | `.text-label-sm` |
+| color (helper, disabled) | `var(--text-muted)` |
+| color (invalid) | `var(--danger-contrast)` |
+| color (required) | `var(--warning-contrast)` |
+| gap (disabled icon) | `var(--spacing-xxs)` |
+
+## Theme gaps
+
+| Figma variable | Temporary primitive | Follow-up |
+|---|---|---|
+| `Components/Form Field/Helper` (Sora 12 / weight 400 / lh 1.3) | `.text-label-sm` (12px / lh 1.5 / weight 500) | `TODO: tokenizar text-form-helper semantic class to match Figma weight 400 + lh 1.3` |
+
+## Accessibility (WCAG 2.1 AA)
+
+- Root is a `
` element so the text is part of the document flow; consumers wire the input with `aria-describedby=""` to expose the helper text to assistive tech.
+- Keyboard map: not focusable (descriptive text); the lock icon for `kind="disabled"` is decorative and marked `aria-hidden="true"`.
+- ARIA: when `kind="invalid"`, consumers should also set `aria-invalid="true"` on the associated input; the helper text content carries the human-readable error.
+- Contrast ≥4.5:1 between each `kind` color and `var(--bg-canvas)` (verified by Storybook a11y addon).
+- `motion-reduce:transition-none motion-reduce:transform-none` not applicable (no motion).
+- Touch target: text is non-interactive; not subject to the 40×40 px rule.
+
+## Stories (Storybook)
+
+- Default
+- Types — composite story rendering every `kind` value side-by-side.
+
+## Constraints — DO NOT
+
+
+
+- Do not add props beyond the Props table above. If you need a prop that is not listed, emit `BLOCKED: missing prop ` and stop — do not invent.
+- Do not add events beyond the Events table above. Same rule for slots and sub-components.
+- Do not invent imports. Every `@aziontech/webkit/*` path must exist in `packages/webkit/package.json#exports`. Every relative import must resolve to a real file. Every npm package must be installed.
+- Do not use HEX/RGB/HSL colors, Tailwind palette names (e.g. `bg-blue-500`), raw typography classes (e.g. `text-sm`), `any`, `@ts-ignore`, or `class` inside `defineProps`.
+- Do not install or import positioning/animation libraries (`@floating-ui/*`, `popper.js`, `tippy.js`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization libs). Use CSS + Vue primitives (``, ``). See `.claude/rules/dependencies.md`.
+- Do not improvise animations. Every `animate-*` / `transition-*` class must come from `packages/theme/src/tokens/semantic/animations.js`; every motion-bearing class pairs with `motion-reduce:*` on the same class string; no component-local `@keyframes`.
+- Do not create class presets in JavaScript (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)`). Variants live on `data-*` attributes consumed by Tailwind `data-[attr=value]:`. All utilities live inline on the root element's `class` attribute. No `