diff --git a/.specs/field-input-group.md b/.specs/field-input-group.md
new file mode 100644
index 000000000..401442d5c
--- /dev/null
+++ b/.specs/field-input-group.md
@@ -0,0 +1,146 @@
+---
+name: field-input-group
+category: inputs
+structure: monolithic
+status: implemented
+spec_version: 1
+figma:
+ url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=3714-10802
+ node_id: 3714:10802
+checksum: 6e03ff3fb8fdf3f005031bc583ef44c78b670d4f95304248730cae0846a69c76
+created: 2026-07-01
+last_updated: 2026-07-01
+---
+
+# FieldInputGroup — Component Spec
+
+## Purpose
+
+Form field wrapper around `InputGroup` that composes `Label`, `InputGroup` (with its own middle `` rendered internally), and `HelperText` into a single vertical stack with consistent spacing. Mirrors `FieldText`'s API and template shape verbatim, except the underlying primitive is `InputGroup` (fixed height, no `size` prop, side slots for icons/text) instead of `InputText`. Use it whenever a text input surrounded by prefix/suffix content needs a visible label or helper/error message.
+
+## Usage
+
+```vue
+
+
+
+
+ https://
+ .com
+
+
+```
+
+## Props
+
+| Prop | Type | Default | Required | JSDoc |
+|---|---|---|---|---|
+| `modelValue` | `string` | `''` | no | Two-way bound value of the internal ``. |
+| `label` | `string` | `''` | no | Text rendered inside the `Label`. When empty, the label row is omitted. |
+| `placeholder` | `string` | `''` | no | Placeholder forwarded to the internal ``. |
+| `helperText` | `string` | `''` | no | Auxiliary text rendered inside `HelperText`. When empty, the helper row is omitted **except** when `disabled` is true — in that case the component falls back to a default disabled message so the lock icon always has matching copy. |
+| `disabled` | `boolean` | `false` | no | Disables the input and the `InputGroup` chrome, and switches the helper to `kind="disabled"` (lock icon). |
+| `readonly` | `boolean` | `false` | no | Marks the internal input read-only; value is visible but not editable. Native pass-through. |
+| `required` | `boolean` | `false` | no | Adds the Required tag to the `Label`, sets `required` on `InputGroup`, and sets native `required` + `aria-required` on the internal ``. |
+| `invalid` | `boolean` | `false` | no | Sets `invalid` on `InputGroup` (danger border) and switches the helper to `kind="invalid"`. Also sets `aria-invalid` on the internal ``. |
+| `inputId` | `string` | `''` | no | `id` for the internal ``; consumed by `Label` via `for` and by `aria-describedby` wiring. Auto-generated via Vue's `useId()` when empty. |
+| `name` | `string` | `''` | no | HTML `name` for the internal `` (form + vee-validate integration). |
+
+## Events
+
+| Event | Payload | Notes |
+|---|---|---|
+| `update:modelValue` | `[value: string]` | Re-emitted from the internal `` on native `input` event. Enables `v-model`. |
+
+## Slots
+
+| Slot | Scope | Notes |
+|---|---|---|
+| `left` | — | Forwarded verbatim to `InputGroup`'s `#left`. Optional prefix content (icon, static text, or a small control). |
+| `right` | — | Forwarded verbatim to `InputGroup`'s `#right`. Optional suffix content. |
+
+## States
+
+- Visual states delegated to `InputGroup`: `default`, `hover`, `focus-within`, `invalid`, `required`, `disabled`.
+- On the field wrapper root: `data-disabled`, `data-invalid`, `data-required` mirror the props (for test/query targeting).
+- `HelperText.kind` computed by precedence: `disabled > invalid > required > 'helper'`.
+- When `disabled` is set and `helperText` is empty, the helper defaults to `'This field is locked.'` (parity with `FieldText`).
+
+## Motion & Animations
+
+_none_
+
+## Tokens
+
+| Region | Token (DESIGN.md) |
+|---|---|
+| stack gap (vertical) | `var(--spacing-xs)` |
+| internal input surface | `var(--bg-surface)` |
+| internal input text | `var(--text-default)` |
+| internal input placeholder | `var(--text-muted)` |
+| internal input padding.x | `var(--spacing-md)` |
+| internal input typography | `.text-label-sm` |
+
+Border / focus / hover / invalid / required / disabled tokens are owned by the nested `InputGroup`.
+
+## Theme gaps
+
+| Figma variable | Temporary primitive | Follow-up |
+|---|---|---|
+| _none_ | — | — |
+
+## Accessibility (WCAG 2.1 AA)
+
+- Visible focus: delegated to `InputGroup` (focus-within ring on the group root). The internal `` renders `focus:ring-0 outline-none` so only the group ring shows.
+- Keyboard map: `Tab` moves through any interactive side-slot content and then the internal ``, in DOM order.
+- ARIA: internal `` receives `aria-invalid`, `aria-required`, and `aria-describedby` pointing at the `HelperText` id when helper text is present or `disabled` forces the fallback. `Label` is linked via `for=`.
+- Contrast ≥4.5:1 for label / input text / helper text against their surfaces.
+- Motion: state color transitions come from `InputGroup` (`duration-150 ease-out`, `motion-reduce:transition-none`).
+- Touch target ≥32×32 px (matches the fixed `h-8` `InputGroup` root).
+
+## Stories (Storybook)
+
+- Default — value + label + placeholder + helperText, no side slots filled.
+- WithSlots — `#left` (`https://`) and `#right` (`.com`) filled.
+- Required — `required=true`, reactive validation flow (label shows Required tag, invalid switches on empty submission).
+- Invalid — `invalid=true` with a message in `helperText`.
+- Disabled — `disabled=true`, helper falls back to the disabled copy.
+- Icons — PrimeIcons in both side slots (`pi pi-globe` left, `pi pi-times` right).
+
+Justification for six stories (deviates from Default+Types+Sizes+state pattern): the component has no `kind` and no `size`, so `Types` and `Sizes` do not apply. `Default`, `WithSlots`, and `Icons` document the composition API (label + helper + side slots). `Required`, `Invalid`, `Disabled` exercise the three state signals the wrapper propagates.
+
+## 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 `