Preview
Installation
Install the runtime packages:
pnpm add @oshon-ai/components @oshon-ai/tokens @oshon-ai/primitives
Or scaffold the component source directly into your codebase (shadcn-style):
pnpm dlx @oshon-ai/cli add linkfield
Wire the tokens into your Tailwind v4 stylesheet:
/* app/globals.css */ @import 'tailwindcss'; @import '@oshon-ai/tokens/css'; @import '@oshon-ai/tokens/tailwind';
New here? Walk through the full setup — prereqs, theming, your first render.
Usage
Import the component and render it. Every component supports the standard tier, size, and disabled props where applicable.
'use client';
import { LinkField } from '@oshon-ai/components';
export default function Example() {
return <LinkField />;
}Basic — controlled
Pick a parent from the directory.
<BasicField />
State matrix — Desktop (m)
Enabled (empty)
Optional Description
Filled
ErrorEmpty
Mandatory Error Description
ErrorFilled
DisabledEmpty
Optional Description
DisabledFilled
<div style={stackCol}>
<p style={sectionHeading}>Enabled (empty)</p>
<LinkField
label="Label"
required
tooltip
description="Optional Description"
placeholder="Search…"
onSearch={() => {}}
/>
<p style={sectionHeading}>Filled</p>
<LinkField
label="Label"
required
tooltip
description="Optional Description"
value="User entered text"
href="#"
onEdit={() => {}}
onClear={() => {}}
/>
<p style={sectionHeading}>ErrorEmpty</p>
<LinkField
label="Label"
required
tooltip
error="Mandatory Error Description"
onSearch={() => {}}
/>
<p style={sectionHeading}>ErrorFilled</p>
<LinkField
label="Label"
required
tooltip
error="Mandatory Error Description"
value="User entered text"
href="#"
onEdit={() => {}}
onClear={() => {}}
/>
<p style={sectionHeading}>DisabledEmpty</p>
<LinkField label="Label" required tooltip description="Optional Description" disabled />
<p style={sectionHeading}>DisabledFilled</p>
<LinkField
label="Label"
required
tooltip
description="Optional Description"
value="User entered text"
href="#"
disabled
/>
</div>State matrix — Mobile
Enabled
Optional Description
Filled
ErrorFilled (mobile = regular, not underlined)
<div style={stackCol}>
<p style={sectionHeading}>Enabled</p>
<LinkField
size="mobile"
label="Label"
required
tooltip
description="Optional Description"
placeholder="Search…"
onSearch={() => {}}
/>
<p style={sectionHeading}>Filled</p>
<LinkField
size="mobile"
label="Label"
required
tooltip
description="Optional Description"
value="User entered text"
href="#"
onEdit={() => {}}
onClear={() => {}}
/>
<p style={sectionHeading}>ErrorFilled (mobile = regular, not underlined)</p>
<LinkField
size="mobile"
label="Label"
required
tooltip
error="Mandatory Error Description"
value="User entered text"
href="#"
onEdit={() => {}}
onClear={() => {}}
/>
</div>Size ladder — xs → mobile
<SizeLadderPlayground />
Styling
Three layers of customization, in order of escape-hatch strength: className overrides → data-attribute targeting → CSS custom properties.
Passing Tailwind classes
Every Oshon component accepts a className prop merged AFTER the component's default classes. Use it to override spacing, color, or size without forking the component.
<LinkField className="ring-2 ring-offset-2 ring-blue-500" />
Data attributes
Oshon components expose their internal state as data-oshon-* attributes so you can target them from CSS without coupling to internal class names. The most common attributes are listed below — see the component's source for the full set.
| Attribute | Values | Description |
|---|---|---|
data-oshon-size | xs · s · m · l · mobile | Visual size axis. Mirrors the `size` prop. |
data-oshon-tier | primary · secondary · tertiary | Visual emphasis tier (Button family). Mirrors the `tier` prop. |
data-oshon-state | enabled · active · error · disabled | Component surface state. Set automatically based on props. |
data-disabled | true · (omitted) | Set when `disabled` is true. Pair with `:disabled` CSS for native input components. |
data-state | open · closed · checked · unchecked · … | Radix-derived state for overlay components (Dialog, Tabs, Toggle, etc.). |
/* Target the secondary tier specifically */
[data-oshon-tier="secondary"] {
--oshon-color-primary-700: var(--my-brand-color);
}Interactive states
Every interactive component supports the standard CSS pseudo- classes plus Tailwind's state variants. Focus rings always use :focus-visible so keyboard users see them but mouse users don't.
:hover/hover:*— pointer hover:focus-visible/focus-visible:*— keyboard focus:active/active:*— pressed:disabled/disabled:*— set via thedisabledprop
Anatomy
The named regions a consumer composes when rendering this component. Each is documented separately so you can target keyboard nav, ARIA labels, and slot props with precision.
labelField label (ReactNode). Desktop: Lato Medium 12/16 neutral-600. Mobile: Lato Regular 14/18 neutral-600.
tooltipOptional info-icon slot next to the required asterisk. Pass `true` for just the icon, or a ReactNode / string for a tooltip body.
secondaryActionRight-justified slot inside the label row for a checkbox or small control.
valueLinked display text. When truthy the field renders the "filled" shape (underlined anchor + edit + clear buttons).
descriptionHelper message under the field. Suppressed when `error` is present.
errorError message under the field. When truthy, sets aria-invalid=true, swaps the border to error-600, and overrides description.
Keyboard
Tab/Shift+Tab: focus each interactive button (search OR edit+clear). Enter/Space: activate. Label associated via the field group id. Description + error wired via aria-describedby on the field group. `required` sets aria-required on the group; `error` sets aria-invalid.
Accessibility
Every Oshon component ships axe-clean. We test in CI on every PR and publish the audit log per component.
- WCAG level
- 2.2 AA
- Screen readers tested
- VoiceOver (macOS), NVDA (Windows)
- Last axe audit
- 2026-04-21
Do / Don't
✓ Do
<LinkField label="Parent" placeholder="Search…" onSearch={openPicker} /><LinkField label="Parent" value="ACME-2019" href="/r/ACME-2019" onEdit={openPicker} onClear={clear} /><LinkField label="Parent" required description="Select the parent record." />
<LinkField label="Parent" required error="Parent is required." />
✗ Don't
<TextField label="Parent" value={parentName} />LinkField encodes the "has it or not" state machine (search vs. edit+clear) plus the underlined-anchor typography. A TextField loses both affordances.
<LinkField label="Parent" required aria-required aria-invalid />
LinkField drives aria-required from `required` and aria-invalid from `error`. Manual wiring fights the state machine.
<LinkField label="X" description="Help" error="Bad" />
The Figma contract is exactly one helper slot: description xor error. When `error` is present, description is suppressed.
Design rationale
Phase 4e.3 visual fidelity refit. Pixel-literal port of Figma DS 3.1 LinkField component set (node 10006:6670) — all 16 variants. Shell shares TextField radius + label/helper rhythm so a form mixing the two reads as one system. Hovered intentionally uses primary-400 (blue-stone-400 #8ad3d3) per the Figma contract — lighter than TextField hover (primary-500) — so the linked resource reads as slightly more "clickable". Mobile ErrorFilled de-underlines the link and swaps to neutral-900 Regular per Figma.