Field Suffix Action
Single trailing suffix slot that swaps a chevron for a clear control on hover, focus, or touch.
Field Suffix Action is the shared trailing slot used by Select, Multi-Select, Combo Box, and search chrome. The chevron is the resting affordance; when the value is clearable, hover or focus swaps in a clear control in the same slot — never side by side.
Kit hosts compose this; prefer those kit fields unless you are building a matching trigger. The host element must carry the group/field class.
When To Use
- You are composing a Select-like trigger and need one trailing slot for chevron and optional clear.
- Search-style fields that hide the chevron and keep a persistent clear (
showChevron={false}). - Nested-in-button hosts (Select trigger): set
as="span"so you do not nest<button>inside<button>. - Prefer Select / Multi-Select / Search Input instead of mounting this on a product form.
Features
| Area | Behavior |
|---|---|
| Resting | Chevron (size-4), optional via showChevron |
| Clear | Same slot; hover / focus-within swap; always visible on hover: none (touch) |
| Nested host | as="span" renders role="button" instead of <button> |
| Signifier | Sets data-field-signifier when the chevron is shown so a locked table cell can hide it |
| Empty | Returns null when neither chevron nor clear should render |
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/field-suffix.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/field-suffix.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/field-suffix.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/field-suffix.jsonWith a namespace: npx shadcn@latest add @f-ui/field-suffix.
registryDependencies: none. Runtime npm: lucide-react.
Usage
import { FieldSuffixAction } from "@/components/f-ui/internals/field-suffix";
<div className="group/field flex h-8 items-center">
<span>{label}</span>
<FieldSuffixAction
clearable={Boolean(value)}
clearLabel="Clear"
onClear={() => setValue("")}
/>
</div>Examples
Resting Chevron
An empty-or-fixed value shows only the chevron. The host uses group/field so hover styles can target the slot.
"use client";
import { FieldSuffixAction } from "@/components/f-ui/internals/field-suffix";
export function FieldSuffixDemo() {
return (
<div
className="group/field flex h-8 w-full max-w-xs items-center justify-between gap-1.5 rounded-lg border border-input px-2.5 text-sm"
>
<span className="min-w-0 flex-1 truncate">Priority shipping</span>
<FieldSuffixAction
clearable={false}
clearLabel="Clear"
onClear={() => {}}
/>
</div>
);
}Clear Swap
Hover or focus the first row to swap the chevron for Clear. The second row is search-style: showChevron={false} keeps clear visible whenever there is a value. On touch devices the first row also keeps clear visible (no hover).
"use client";
import { useState } from "react";
import { FieldSuffixAction } from "@/components/f-ui/internals/field-suffix";
export function FieldSuffixClearableDemo() {
const [method, setMethod] = useState("Priority shipping");
const [query, setQuery] = useState("east dock");
return (
<div className="max-w-xs space-y-3">
<div className="group/field flex h-8 w-full items-center justify-between gap-1.5 rounded-lg border border-input px-2.5 text-sm">
<span className="min-w-0 flex-1 truncate">
{method || "Choose a method"}
</span>
<FieldSuffixAction
clearable={method.length > 0}
clearLabel="Clear"
onClear={() => setMethod("")}
/>
</div>
<div className="group/field relative">
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
aria-label="Filter"
className="h-8 w-full rounded-lg border border-input bg-transparent pe-8 ps-2.5 text-sm"
/>
<span className="absolute inset-y-0 inset-e-0 flex items-center pe-1">
<FieldSuffixAction
clearable={query.length > 0}
showChevron={false}
clearLabel="Clear filter"
onClear={() => setQuery("")}
/>
</span>
</div>
</div>
);
}Nested In a Button
When the host is a <button> (Select trigger), pass as="span". Clear still activates from click, Enter, and Space, and does not toggle the host.
"use client";
import { useState } from "react";
import { FieldSuffixAction } from "@/components/f-ui/internals/field-suffix";
export function FieldSuffixNestedButtonDemo() {
const [value, setValue] = useState("Priority shipping");
return (
<button
type="button"
className="group/field flex h-8 w-full max-w-xs items-center justify-between gap-1.5 rounded-lg border border-input px-2.5 text-left text-sm"
>
<span className="min-w-0 flex-1 truncate">
{value || "Choose a method"}
</span>
<FieldSuffixAction
as="span"
clearable={value.length > 0}
clearLabel="Clear"
onClear={() => setValue("")}
/>
</button>
);
}API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
clearable | boolean | — | When true, the slot can swap in the clear control. |
clearLabel | string | — | Accessible name for the clear control. |
onClear | () => void | — | Fires on clear; click is stopped so the host popover does not toggle. |
as | "span" | "button" | "button" | "span" when the host is already a <button>. |
showChevron | boolean | true | Resting chevron. Set false for search-style fields. |
chevronClassName | string | — | Extra classes on the chevron (open-state rotation). |
className | string | — | Merged onto the slot wrapper. |
data-slot | string | — | Optional slot name for the host (e.g. multi-select-suffix). |
See Also
- Select — trigger uses
as="span" - Multi-Select — anchor uses
as="button" - Combo Box
- Search Input
- Field Lock Affordance — table cells hide this signifier when locked