Resolved field
Resolved field is a neobrutalist React component from Cruise UI. Shows the result of a lookup under its input, e.g. the account name for an account number: "Resolving…" with a spinner, then the value or an error with Try again. Install it with npx shadcn@latest add @cruiseui/cruise-resolved-field.
Install
Terminal
npx shadcn@latest add @cruiseui/cruise-resolved-fieldFirst time? Installation shows the one-off components.json setup (the @cruiseui registry) and how to turn the theme on.
Example
Corners
Try 0123456789 (found) or 9876543210 (not found).
Account name
Enter the account number to see the name.
"use client";
import { useEffect, useState } from "react";
import { CruiseInput } from "@/components/cruise/input";
import { CruiseResolvedField, type CruiseResolveStatus } from "@/components/cruise/resolved-field";
// A pretend lookup: in your app, call your bank-lookup API.
const fakeLookup = (account: string) =>
new Promise<string | null>((resolve) => setTimeout(() => resolve(account.startsWith("0") ? "ADA OKAFOR" : null), 900));
export default function ResolvedFieldExample() {
const [account, setAccount] = useState("");
const [result, setResult] = useState<{ status: CruiseResolveStatus; name?: string; for?: string }>({ status: "idle" });
const complete = /^\d{10}$/.test(account);
// Shown state: idle until 10 digits; otherwise the lookup for this number.
const status: CruiseResolveStatus = !complete ? "idle" : result.for === account ? result.status : "resolving";
useEffect(() => {
if (!complete) return;
let live = true;
fakeLookup(account).then((name) => live && setResult({ status: name ? "resolved" : "error", name: name ?? undefined, for: account }));
return () => {
live = false;
};
}, [account, complete]);
return (
<div className="flex max-w-md flex-col gap-4">
<CruiseInput label="Account number" inputMode="numeric" maxLength={10} hint="Try 0123456789 (found) or 9876543210 (not found)." value={account} onChange={(e) => setAccount(e.target.value.replace(/\D/g, ""))} />
<CruiseResolvedField status={status} value={result.name} onRetry={() => setResult({ status: "idle" })} />
</div>
);
}
<span class="field-label" id="acct-label">Account name</span>
<div class="resolved" data-state="resolved" role="status" aria-live="polite" aria-describedby="acct-label">
<svg aria-hidden="true"><!-- circle-check --></svg><span>ADA OKAFOR</span>
</div>Needs theme.css. Interactive parts (menus, tabs, drawers) also need the script in the spec for that component.
When to use it
When to use
Account-name lookups in transfers, business-name checks, any value fetched from what someone typed.
When not to
Validating the input's own format (use the field's error).
Props
CruiseResolvedField
| Prop | Type | Default | Description |
|---|---|---|---|
label? | string | "Account name" | What the value is, shown above it. |
status | CruiseResolveStatus | — | — |
value? | ReactNode | — | The resolved value, e.g. "ADA OKAFOR". |
error? | ReactNode | "We couldn't find that account. Check the number and bank." | Shown when status is "error". |
placeholder? | ReactNode | "Enter the account number to see the name." | Shown before a lookup starts (status "idle"). |
resolvingText? | ReactNode | "Resolving…" | — |
onRetry? | (() => void) | — | Adds a "Try again" button to the error. |
className? | string | — | — |
Usage
- Keep the status in your component: set status to "resolving" when the account number and bank are complete, then "resolved" with the name or "error".
- Debounce the lookup (about 400ms) and ignore answers that arrive after the input changed.
Accessibility
- role="status" with aria-live="polite": each change is read out.
- Resolved and error states use an icon and words, not colour alone.
- "Try again" is a real button.