@fastyshop/ui
v0.13.1
Published
`@fastyshop/ui` contains the shared FastyShop product tokens, React components, and public hooks. The package ships ESM, TypeScript declarations, Tailwind CSS v4 integration, the Inter variable font, the FastyShop Empty and Field patterns, Dialog, AlertDi
Readme
@fastyshop/ui
@fastyshop/ui contains the shared FastyShop product tokens, React components,
and public hooks. The package ships ESM, TypeScript declarations, Tailwind CSS
v4 integration, the Inter variable font, the FastyShop Empty and Field patterns,
Dialog, AlertDialog, Drawer, DrawerMenu, AdaptiveDialog, AdaptiveMenu, Menu,
Alert, Accordion, InputGroup, NumberField, OTPField, Tooltip, CheckboxGroup and
ButtonLink and Pagination components, BrandMark, BrandWordmark, BrandLockup, Avatar, Badge, Button, Label, Link, Meter, Progress,
Select, Separator, Skeleton, Spinner, Tabs, Toast and Typography components and primitives, and the
useComboboxFilter, useCopyToClipboard and useMediaQuery hooks.
Install
Install an exact published version from the public npm registry. No npm login
or read token is required. This source prepares 0.13.1; the command below
applies after its separately approved publication. Until then, 0.12.0 remains
the latest published version and does not include Pagination.
pnpm add @fastyshop/[email protected]React and React DOM are peer dependencies. Supported versions are >=19.2.0 <20. The package requires Node.js >=24.18.0 <25 for build and server-side use.
CSS and themes
Import the runtime stylesheet once near the root of the application:
@import "@fastyshop/ui/styles.css";Light is the default theme. Set data-fs-theme="dark" on the document root or on a bounded subtree to use the dark semantic values:
<html data-fs-theme="dark"></html>The runtime stylesheet includes the bundled Inter variable font and the package component styles. If your application already loads Inter, keep one authoritative font declaration and verify that its family and weights match the package contract.
Tailwind CSS v4 consumers can additionally import the semantic utility bridge:
@import "tailwindcss";
@import "@fastyshop/ui/tailwind-theme.css";The bridge maps fs-prefixed utilities to the runtime CSS variables. It does not copy token values into the consuming application. It deliberately replaces Tailwind's default responsive variants with only md: 48rem and lg: 64rem.
Responsive layout
The page shell remains full-width. Use the responsive semantic grid without a global container or page-level max-width:
<main
class="grid min-w-0 grid-cols-4 gap-x-fs-grid-column-gap md:grid-cols-8 lg:grid-cols-12"
></main>Forms and prose opt into local measures with w-full max-w-(--fs-layout-content-form-max) or w-full max-w-(--fs-layout-content-prose-max). Wide tables and timelines own a labelled, keyboard-focusable local scroller; they must not make body scroll horizontally. See the repository's docs/responsive-layout.md for safe-area, overflow, container-query, and localization guidance.
Overlay authors must additionally follow the repository's
docs/overlay-viewport-environment.md: free-standing surfaces use the maximum
of page gutter and safe area, flush-edge content adds panel padding to the
maximum current occlusion, and a form-heavy bottom Drawer delegates software
keyboard alignment to Base UI's public Drawer.VirtualKeyboardProvider.
The repository's docs/overlay-interaction-boundaries.md also defines semantic
topmost ownership, focus entry/return, modality, dismissal, synchronous close
prevention and Toast coexistence. Base UI remains the sole mechanics owner;
consumers do not add document-level focus or dismissal managers.
The repository-only docs/overlay-conformance.md smoke verifies only the
shared Foundation boundaries before public overlay families exist; it does not
materialize their component behavior and is not part of this package's exports
or installed payload.
Brand
import { BrandLockup } from "@fastyshop/ui";
export function ProductIdentity() {
return <BrandLockup variant="on-light" decorative={false} />;
}Brand uses the outlined J6.4 artwork, without a runtime font or automatic theme
selection. variant and decorative are required. Minimum SVG viewport widths
are 16px for BrandMark, 128px for BrandWordmark and 160px for BrandLockup;
defaults are 24/128/192px. Use proportional width, not independent height or
fill overrides. Leave clear space of 32 × width / viewBoxWidth on every side.
Inside a named link, use a decorative Brand and name the link for its destination.
Raw assets resolve through @fastyshop/ui/brand/manifest.json,
@fastyshop/ui/brand/assets/mark/fastyshop-mark-on-light.svg,
@fastyshop/ui/brand/assets/app-icons/app-icon-192.png and
@fastyshop/ui/brand/licenses/Saira-OFL.txt. These are file-resolution/copy
subpaths, not a universal JavaScript image loader. All asset and license paths
in the manifest resolve within this bundle; upstream proof/evidence references
are provenance only and are not distributed. The source-copy registry provides
React/TS/CSS only. See the repository's docs/brand.md for the complete contract.
Menu
"use client";
import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@fastyshop/ui";
export function OrderActions({ onArchive }: { onArchive: () => void }) {
return (
<Menu>
<MenuTrigger>Actions</MenuTrigger>
<MenuPopup>
<MenuItem onClick={onArchive}>Archive</MenuItem>
</MenuPopup>
</Menu>
);
}Menu supports controlled and uncontrolled opening, native links, checkbox, switch and radio items, hover opening and submenus. The caller owns routing, permissions, localized labels and asynchronous actions. Base UI owns keyboard, focus and dismissal mechanics. See the Menu contract for all 15 runtime exports, 16 public types and composition recipes.
Pagination
Use a named Pagination with the manual compound parts for caller-owned layouts,
or use PaginationControls for a bounded numbered window. Page state, routing,
labels and number formatting remain application-owned.
import { Pagination, PaginationControls } from "@fastyshop/ui";
<Pagination aria-label="Order pages">
<PaginationControls
mode="link"
page={10}
pageCount={20}
labels={{
previous: "Previous page",
next: "Next page",
page: (page) => `Page ${page}`,
}}
getHref={(page) => `/orders?page=${page}`}
/>
</Pagination>;Use mode="button" with onPageChange for application-controlled transitions.
Manual composition exports PaginationList, PaginationItem, PaginationPage,
PaginationPrevious, PaginationNext and PaginationEllipsis. Import the shared
stylesheet once for both forms.
Dialog and confirmation
"use client";
import {
Dialog,
DialogTrigger,
DialogPopup,
DialogTitle,
DialogDescription,
DialogClose,
} from "@fastyshop/ui";
export function NoteDialog() {
return (
<Dialog>
<DialogTrigger>Open note</DialogTrigger>
<DialogPopup closeButtonLabel="Close note">
<DialogTitle>Note</DialogTitle>
<DialogDescription>Review this message before continuing.</DialogDescription>
<DialogClose>Done</DialogClose>
</DialogPopup>
</Dialog>
);
}Dialog includes responsive desktop/mobile presentation, reduced motion, Header/Panel/Footer composition, three modality profiles, synchronous close veto, nested surfaces and typed detached/multiple triggers. Native forms and async state remain caller-owned. See the Dialog contract for public props and Menu-to-Dialog and dirty-draft recipes.
The controlled AlertDialog companion requires a visible Title, Description and
text Cancel (AlertDialogClose). Initial focus chooses Cancel; outside interaction
never confirms or dismisses. Confirm is a caller-owned Button, separate from
Cancel/Escape. See the AlertDialog contract
for the complete seven-part composition and pending/error handling.
Form
Form, FormProps and FormErrors are included in 0.9.0. Form renders a
native form and composes with named Field/FieldControl and Input, Textarea,
Checkbox, RadioGroup, Switch, Select and Combobox. Registered invalid fields
block submission and receive first-invalid focus. Pass localized messages
through errors keyed by Field name; an empty <FieldError /> displays the
active field message.
The caller owns onSubmit, values and payload creation, including FormData.
For client validation or requests, call event.preventDefault() synchronously.
Zod schemas, React Query mutations, pending, duplicate-submit protection and
retry belong to the application; these libraries are not package runtime
dependencies. Form provides no intrinsic layout or universal reset-all engine.
See the Form contract
for native and external validation recipes.
Combobox and native forms
Combobox supports direct Field composition for single or ordered multiple
selection, with search in the main control or in the popup. Field labels,
descriptions and errors reference the primary Input, ChipsInput or external
Trigger. Root name, form and required participate in ordinary HTML forms:
FormData contains accepted stable keys, while the search query stays separate.
Native validation focuses the primary control, and reset restores mounted
uncontrolled keys/query without change callbacks, including inside a ShadowRoot.
Controlled axes remain caller-owned.
See the Combobox contract for composition recipes and the accepted input-outside accessibility limitation.
Select
Provide the complete item catalog on the root and render matching items inside the popup:
import {
Select,
SelectItem,
SelectLabel,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@fastyshop/ui";
const countries = [
{ value: "kz", label: "Kazakhstan" },
{ value: "kg", label: "Kyrgyzstan" },
] as const;
<Select items={countries} defaultValue={null} name="country">
<SelectLabel>Country</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Choose a country" />
</SelectTrigger>
<SelectPopup>
{countries.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>;See the repository's docs/select.md for object values, multiple selection,
groups, form behavior and the full composition contract.
Toast
Mount one provider near the application shell, then add notifications through the shared manager:
import { ToastProvider, toastManager } from "@fastyshop/ui";
<ToastProvider closeLabel="Close notification" viewportLabel="Notifications">
{children}
</ToastProvider>;
toastManager.add({ title: "Order created", type: "success" });See the repository's docs/toast.md for actions, promise notifications,
timeouts, portal ownership and the full lifecycle contract.
Button
import { Button } from "@fastyshop/ui";
export function SaveAction() {
return <Button>Save</Button>;
}Button is a client component backed by Base UI. The package owns that runtime dependency; consumers do not install Base UI, Radix, or shadcn directly. Use Link or ButtonLink for navigation, keep loading controlled, and provide a non-empty aria-label for icon-only buttons.
Link
import { Link } from "@fastyshop/ui";
export function CatalogLink() {
return <Link href="/catalog">Catalog</Link>;
}Link is the server-safe text-navigation primitive. It preserves native anchor semantics and supports a controlled render callback for framework routers without adding a framework dependency to the package.
ButtonLink
import { ButtonLink } from "@fastyshop/ui";
export function CheckoutLink() {
return <ButtonLink href="/checkout">Continue to checkout</ButtonLink>;
}ButtonLink is server-safe button-shaped navigation. It shares Button's visual recipe but always materializes an anchor-compatible root and does not expose action-only loading, disabled, press, or form behavior.
Avatar
import { Avatar, AvatarFallback, AvatarImage } from "@fastyshop/ui";
export function MerchantAvatar() {
return (
<Avatar>
<AvatarImage alt="Aruzhan Bek" src="/merchant-avatar.jpg" />
<AvatarFallback>AB</AvatarFallback>
</Avatar>
);
}Avatar owns image load/failure switching, size and shape. Consumers own the image request, alternative text, fallback content and surrounding interaction.
Badge
import { Badge } from "@fastyshop/ui";
export function PaymentState() {
return <Badge variant="success">Paid</Badge>;
}Badge is a compact label for status, category and short metadata. Its native
span, a and button roots preserve their distinct semantics.
Label
import { Label } from "@fastyshop/ui";
export function ShopNameLabel() {
return <Label htmlFor="shop-name">Shop name</Label>;
}Label is the server-safe native label primitive. Consumers own stable control IDs, field lifecycle, validation and description or error association.
Field
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from "@fastyshop/ui";
export function EmailField({ showErrors }: { showErrors: boolean }) {
return (
<Field touched={showErrors}>
<FieldLabel requirement="Required">Email</FieldLabel>
<FieldControl required type="email" />
<FieldDescription>Used for order notifications.</FieldDescription>
<FieldError match={showErrors ? "valueMissing" : false}>Enter your email.</FieldError>
</Field>
);
}Field is the single-control form composition. It owns label, description and error association while consumers own localized copy, validation rules and error timing.
Meter
import { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue } from "@fastyshop/ui";
export function PublishedProductsMeter() {
return (
<Meter
getAriaValueText={(_, rawValue) => `Used ${rawValue} of 200 products`}
max={200}
tone="success"
value={69}
>
<MeterLabel>Published products</MeterLabel>
<MeterValue>{(_, rawValue) => `${rawValue} / 200`}</MeterValue>
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</Meter>
);
}Meter is a read-only scalar range primitive backed by Base UI. It normalizes visual and ARIA output to the configured finite range while preserving the raw value for formatter callbacks. Consumers own units, thresholds, localized status copy and announcements; use Progress for an ongoing operation. See the repository guide for the complete composition, accessibility, tone and registry contracts.
Progress
import { Progress } from "@fastyshop/ui";
import { ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue } from "@fastyshop/ui";
export function CatalogUploadProgress() {
return (
<Progress
getAriaValueText={(_, rawValue) => `Uploaded ${rawValue} of 512 MB`}
max={512}
value={502}
>
<ProgressLabel>Catalog upload</ProgressLabel>
<ProgressValue>{(_, rawValue) => `${rawValue} / 512 MB`}</ProgressValue>
<ProgressTrack>
<ProgressIndicator />
</ProgressTrack>
</Progress>
);
}Progress is a read-only operation range backed by Base UI. A finite value
renders determinate progress; value={null} is the only indeterminate state.
Consumers own requests, result state, localized copy and announcements. See the
repository guide
for the complete composition, accessibility, motion and registry contracts.
Tabs
import { Tabs, TabsList, TabsPanel, TabsTab } from "@fastyshop/ui";
export function ProductSections() {
return (
<Tabs defaultValue="overview">
<TabsList aria-label="Product sections" variant="underline">
<TabsTab value="overview">Overview</TabsTab>
<TabsTab value="details">Details</TabsTab>
</TabsList>
<TabsPanel value="overview">Overview content</TabsPanel>
<TabsPanel value="details">Details content</TabsPanel>
</Tabs>
);
}Tabs is a client primitive backed by Base UI. It owns tab semantics, generated Tab/Panel relationships, roving keyboard focus and one private moving indicator. Callers provide an explicit controlled or uncontrolled string selection, one localized tab-list name, visible labels and matching panels. Routing, data loading, overflow treatment and form submission policy stay outside the primitive. See the repository guide for the complete behavior, lifecycle and registry contracts.
Empty
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@fastyshop/ui";
export function OrdersEmpty() {
return (
<Empty aria-labelledby="orders-empty-title">
<EmptyHeader>
<EmptyTitle id="orders-empty-title">No orders yet</EmptyTitle>
<EmptyDescription>New orders will appear here.</EmptyDescription>
</EmptyHeader>
</Empty>
);
}Empty is a server-safe composition pattern with default and compact
density recipes. Consumers choose which subcomponents to render and own all
localized copy, state, announcements and actions. The optional media wrapper is
decorative; titles remain real h1–h6 headings. See the
repository guide
for the full composition, media, accessibility and registry contracts.
Typography
import { Typography } from "@fastyshop/ui";
export function PageTitle() {
return (
<Typography as="h1" variant="heading-lg">
Orders
</Typography>
);
}variant is required and selects one of the eleven semantic typography roles;
as independently selects span (default), p, div, h1–h6,
blockquote, or figcaption. Typography is server-safe, inherits color,
neutralizes native margins, and leaves wrapping and document hierarchy to the
consumer. Component-owned labels and titles keep their owning component DOM
instead of adding a nested Typography wrapper. See the
repository guide
for the complete API and role guidance.
Spinner
import { Spinner } from "@fastyshop/ui";
export function OrdersStatus() {
return <Spinner decorative={false} label="Loading orders" />;
}Spinner is server-safe and decorative by default. Announced use requires a
caller-owned localized label. The consumer owns loading lifecycle, aria-busy,
completion, errors and retries. Sizes are sm, md and lg; color inherits
from context, reduced motion disables rotation, and forced colors follow the
system-adjusted foreground. See the
repository guide
for the full semantic and registry contract.
Separator
import { Separator } from "@fastyshop/ui";
export function OrderSummaryBoundary() {
return <Separator />;
}Separator is server-safe and decorative by default. Set decorative={false}
only when the divider is a meaningful document boundary; choose horizontal or
vertical orientation and keep surrounding spacing and vertical length in the
consumer. See the
repository guide
for the complete semantics and composition contract.
Skeleton
import { Skeleton } from "@fastyshop/ui";
export function OrderCardPlaceholder() {
return <Skeleton className="order-card-placeholder" />;
}Skeleton is a decorative, server-safe loading placeholder. The consumer owns
its geometry, changing-region aria-busy state and localized status message.
Reduced motion disables pulsing, while forced colors preserve a visible static
boundary. See the
repository guide
for sizing and accessibility guidance.
useMediaQuery
"use client";
import { useMediaQuery } from "@fastyshop/ui";
export function ResponsiveBehavior() {
const isDesktop = useMediaQuery("desktop");
const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
return <span>{isDesktop && !reduceMotion ? "Enhanced motion" : "Standard view"}</span>;
}FastyShop shorthand maps tablet to 48rem and desktop to 64rem;
max-* uses an exclusive upper boundary. Structured input supports min,
max, and pointer. Server rendering and initial hydration return false,
then React synchronizes the client match. Prefer CSS media/container queries
for visual layout. The current browser contract uses modern MediaQueryList
events and Media Queries Level 4 range syntax. The full API, browser boundary,
and raw-query examples are documented in the repository guide.
useCopyToClipboard
"use client";
import { useCopyToClipboard } from "@fastyshop/ui";
export function CopyOrderId({ orderId }: { orderId: string }) {
const { copyToClipboard, isCopied } = useCopyToClipboard();
return (
<button onClick={() => void copyToClipboard(orderId)} type="button">
{isCopied ? "Copied" : "Copy order id"}
</button>
);
}copyToClipboard resolves to success, empty, unsupported, or failed.
Only an empty string skips the browser API; other strings are passed to
navigator.clipboard.writeText unchanged. isCopied is transient
presentation state for the latest call. Consumers own localized and
accessible feedback, analytics, and recovery behavior. The full result,
lifecycle, server, and source-copy contracts are documented in the
repository guide.
Package boundary
The public exports are:
@fastyshop/ui@fastyshop/ui/styles.css@fastyshop/ui/tailwind-theme.css
Source files, tests, Storybook, registry tooling, and private design evidence are not part of the npm tarball. The package is MIT licensed; bundled Inter files retain the SIL Open Font License 1.1 and adapted Empty material retains its upstream MIT terms as described in THIRD_PARTY_NOTICES.md.
