@matthieuglaisner/react-ui-components-library
v1.1.0
Published
React + Tailwind CSS + TypeScript UI components
Maintainers
Readme
@matthieuglaisner/react-ui-components-library
React + Tailwind CSS + TypeScript UI components. Most building blocks are Tailwind-only; overlays, menus, tabs, and several form primitives wrap Radix UI for accessibility (focus traps, portals, keyboard support). Everything ships as compiled JS plus TypeScript declarations; your app must run Tailwind so utility classes in dist are generated.
Requirements
- React 18 or 19 (
reactandreact-dom) - Tailwind CSS in the host app (v3-style
contentconfig below; adapt if you use Tailwind v4) - Radix UI packages — required peers for components that import them. Install the
@radix-ui/react-*packages you use (see peerDependencies for exact names and ranges). If you use every exported Radix-backed piece, install all listed Radix peers. - react-router-dom 6.28+ or 7.x is optional. Install it only if you use
Linkwithas={NavLink}oras={RouterLink}.
This package does not ship a separate CSS bundle. Class names live in dist/**/*.js (and .cjs), and your Tailwind build must scan those files.
Installation
npm install @matthieuglaisner/react-ui-components-libraryInstall Radix peers as needed (example — matches current package.json peer ranges):
npm install @radix-ui/react-accordion @radix-ui/react-alert-dialog @radix-ui/react-dialog @radix-ui/react-popover @radix-ui/react-scroll-area @radix-ui/react-select @radix-ui/react-slider @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-toast @radix-ui/react-toggle-group @radix-ui/react-tooltipTo install from Git instead of the registry:
npm install github:MattGlsn/ui-components-libraryAdjust the URL to your fork or organization.
Tailwind setup (required)
Tailwind’s JIT compiler only emits utilities for class names it finds in the content paths you configure. Because this library publishes dist only, add a glob that includes the ESM and CJS bundles (.js and .cjs):
// tailwind.config.js (or .ts)
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@matthieuglaisner/react-ui-components-library/dist/**/*.{js,cjs}",
],
theme: {
extend: {},
},
plugins: [],
};Adjust the first entries to match your app. If the package is hoisted differently, point the glob at the folder that contains @matthieuglaisner/react-ui-components-library/dist.
Dark mode
Components use Tailwind dark: variants (primary #6a7cbd in light, #8fa2e6 in dark). Set darkMode to "class" or "media" in your Tailwind config. Link is a styled text link (hover color only; use aria-current="page" or NavLink for the current route). For SPA routes use <Link as={RouterLink} to="…"> or <Link as={NavLink} to="…"> from react-router-dom when needed. See Storybook for per-component focus, error, and layout details.
Component catalog
Exports are named (tree-shakeable) from the package root, for example import { DialogRoot, Table } from "@matthieuglaisner/react-ui-components-library".
| Area | Components |
| ---- | ---------- |
| Layout | Container, Divider, Grid, Group, Section, Stack |
| Typography | Label, Text, Title |
| Feedback | Alert, Badge, Progress, Skeleton, Spinner, Toast |
| Toasts (Radix) | ToasterProvider, ToasterViewport, ToasterRoot, ToasterTitle, ToasterDescription, ToasterClose, ToasterAction — stackable notifications; compose with Radix patterns (see Storybook). |
| Overlays | DialogRoot, DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogClose, DialogPortal, DialogOverlay |
| Confirm | AlertDialogRoot, AlertDialogTrigger, AlertDialogContent, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, … |
| Drawer | DrawerRoot, DrawerTrigger, DrawerContent, DrawerTitle, DrawerDescription, DrawerClose, DrawerOverlay |
| Floating | PopoverRoot, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverPortal |
| Tooltip | TooltipProvider, TooltipRoot, TooltipTrigger, TooltipContent, TooltipPortal — wrap the app (or layout) with TooltipProvider where you use tooltips. |
| Select | SelectRoot, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator, SelectPortal, SelectViewport |
| Navigation | TabsRoot, TabsList, TabsTrigger, TabsContent; AccordionRoot, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent; Pagination; Stepper; Navbar; DropdownMenu; Breadcrumbs |
| Data display | Card; Table, TableHeader, TableBody, TableRow, TableHeadCell, TableCell; Avatar; EmptyState |
| Forms | Button, Input, TextArea, Checkbox, CheckboxList, Radio, RadioList, Form, Switch, Slider, FileInput, SegmentedControlRoot, SegmentedControlItem |
| Content / chrome | Link |
| Utility | ScrollArea; useClickAway |
Toast vs Toaster: Toast is a single presentational banner (variants, dismiss). Toaster* primitives are Radix-based and suit queued, viewport-anchored notifications; use them when you need provider + multiple live regions.
Usage
import { useState } from "react";
import {
Breadcrumbs,
Button,
Checkbox,
CheckboxList,
DropdownMenu,
Form,
Input,
Link,
Navbar,
TextArea,
Title,
Toast,
type BreadcrumbItem,
} from "@matthieuglaisner/react-ui-components-library";
export function Example() {
const [agree, setAgree] = useState(false);
const [channels, setChannels] = useState<string[]>(["email"]);
const [email, setEmail] = useState("");
const [formError, setFormError] = useState<string | undefined>();
const notifyItems = [
{ id: "email", label: "Email" },
{ id: "sms", label: "SMS" },
];
const breadcrumbItems: BreadcrumbItem[] = [
{ label: "Home", href: "#" },
{ label: "Projects", href: "#" },
{ label: "Project Alpha" },
];
return (
<div className="flex flex-col gap-6">
<Title as="h2" size="md" accent>
Example
</Title>
<Toast type="info" title="Tip" onDismiss={() => {}}>
Optional body text.
</Toast>
<Navbar
aria-label="Example"
brand={<span className="text-lg font-semibold text-[#6a7cbd]">Acme</span>}
linksAlign="center"
trailing={<Button size="sm">Sign in</Button>}
>
<Link href="#" aria-current="page">
Home
</Link>
<Link href="#">Docs</Link>
</Navbar>
<Breadcrumbs items={breadcrumbItems} />
<div className="flex flex-wrap gap-3">
<Button variant="primary" size="md">
Save
</Button>
<Button variant="secondary" size="sm">
Cancel
</Button>
<Button variant="tertiary" fullWidth disabled>
Disabled
</Button>
<Button className="uppercase tracking-wide">Custom classes</Button>
</div>
<div className="flex w-full max-w-sm flex-col gap-3">
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]"
helperText="We’ll only use this for account recovery."
/>
<Input type="password" placeholder="Password" aria-label="Password" />
<Input size="sm" invalid placeholder="Fix validation errors" aria-label="Invalid example" />
<Input fullWidth placeholder="Full width in a block layout" aria-label="Wide field" />
</div>
<TextArea
label="Notes"
placeholder="Add details…"
rows={4}
helperText="Optional. Shown below the field when there is no error."
/>
<TextArea error="This field is required." defaultValue="" rows={3} />
<Form
className="w-full max-w-sm"
error={formError}
onSubmit={(e) => {
e.preventDefault();
setFormError("Example error — change the field below to clear it.");
}}
submitLabel="Submit form"
>
<Input
label="Field in Form"
name="inForm"
fullWidth
placeholder="Type to clear the error"
onChange={() => setFormError(undefined)}
/>
</Form>
<div className="flex flex-col gap-2">
<Checkbox label="Remember me" defaultChecked />
<Checkbox aria-label="Standalone option" />
<Checkbox
label="I agree (controlled)"
checked={agree}
onChange={(e) => setAgree(e.target.checked)}
/>
<CheckboxList
legend="Notifications"
items={notifyItems}
orientation="column"
value={channels}
onValueChange={setChannels}
name="notify"
/>
<CheckboxList
aria-label="Layout row example"
items={[
{ id: "a", label: "Alpha" },
{ id: "b", label: "Bravo" },
{ id: "c", label: "Charlie" },
]}
orientation="row"
defaultValue={["b"]}
/>
<DropdownMenu
triggerLabel="Actions"
items={[
{ id: "edit", label: "Edit" },
{ id: "copy", label: "Copy" },
{ id: "remove", label: "Remove", disabled: true },
]}
/>
</div>
</div>
);
}Button props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| variant | "primary" (default), "secondary", or "tertiary" |
| size | "sm", "md" (default), or "lg" — text and padding scale together |
| fullWidth | When true, the button spans the width of its container |
| disabled | Native disabled behavior and muted appearance |
| className | Merged with the component’s base classes |
| … | Other native <button> attributes are forwarded (for example type, onClick, aria-*) |
Form props (summary)
| Prop | Description |
| -------------------- | --------------------------------------------------------------------------- |
| showSubmitButton | When false, the built-in submit Button is not rendered. Default true. |
| submitLabel | Label for the default submit button. Default "Submit". |
| submitDisabled | When true, disables the default submit (combined with submitButtonProps.disabled). |
| submitButtonProps | Props forwarded to the default submit Button except type (always "submit") and children (from submitLabel). |
| error | Form-level message (ReactNode). Empty or whitespace-only strings are ignored. Renders in role="alert" with error styling; sets aria-invalid on the form; merges into aria-describedby. While shown, the default submit is disabled — clear error to re-enable. |
| className | Merged onto the <form>. |
| … | Other native <form> attributes are forwarded (for example onSubmit, method, action, ref, aria-describedby). |
Input props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| type | Any native input type (defaults to "text"). |
| label | Optional ReactNode. When set, wraps the field in a <label> and wires id / htmlFor if you omit id. |
| size | "sm", "md" (default), or "lg" — padding and text scale (not the HTML size attribute; use className / style for character width if needed). |
| fullWidth | When true, the input spans the width of its container. |
| helperText | Optional ReactNode rendered below the field and linked via aria-describedby (merged with your aria-describedby). Color follows status: invalid (enabled) is red; disabled is muted (wins over invalid). |
| invalid | When true, sets aria-invalid and applies error border and focus ring. |
| disabled | Native disabled behavior and muted appearance |
| className | Merged with the component’s base classes |
| … | Other native <input> attributes are forwarded. For checkboxes, consider the library Checkbox for tailored styling. |
TextArea props (summary)
| Prop | Description |
| ------------- | --------------------------------------------------------------------------- |
| label | Optional label; associates with the control via id or a generated id |
| helperText | Hint below the field (hidden when error is a non-empty string) |
| error | true sets aria-invalid; a string also shows that message below the field |
| size | "sm", "md" (default), or "lg" — text, padding, and minimum height |
| fullWidth | When true, the textarea spans the width of its container |
| disabled | Native disabled behavior and muted appearance |
| className | Merged with the component’s base classes |
| … | Other native <textarea> attributes are forwarded (for example rows, value, onChange, aria-*) |
Title props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| as | "h1" (default) through "h6" — semantic heading element |
| children | Heading content |
| size | "xs", "sm", "md" (default), "lg", or "xl" — visual scale |
| align | "left" (default), "center", or "right" |
| accent | When true, uses the library primary color for the text |
| truncate | When true, single-line ellipsis overflow |
| className | Merged with the component’s base classes |
| … | Other native heading attributes are forwarded for the chosen as tag |
Radio props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| children | Optional label content; when set, wraps the input in an associated <label>. |
| className | Merged with the component’s base classes |
| … | Other native <input> attributes are forwarded except type (always "radio"). |
RadioList props (summary)
| Prop | Description |
| ----------------- | --------------------------------------------------------------------------- |
| name | Shared name for each radio in the group. |
| options | Array of { value, label, disabled? }. |
| orientation | "vertical" (default) or "horizontal". |
| value / defaultValue / onChange | Controlled or uncontrolled single selection (native change events). |
| legend | Optional fieldset legend; otherwise set aria-label on the fieldset. |
| disabled | When true, all options are non-interactive. |
| className | Merged onto the <fieldset>. |
| … | Other native <fieldset> attributes are forwarded (except children / onChange). |
DropdownMenu props (summary)
| Prop | Description |
| ----------------- | --------------------------------------------------------------------------- |
| items | Array of { id, label, disabled? }. |
| onItemSelect | Optional (id: string) => void when an enabled item is chosen; menu closes afterward. |
| triggerLabel | Use either this or renderTrigger: built-in <button> text. |
| renderTrigger | (props: DropdownMenuTriggerProps) => ReactNode — spread props onto your button (ref, onClick, ARIA). |
| align | "start" (default) or "end" — horizontal alignment of the panel under the trigger. |
| open / onOpenChange | Optional controlled open state. |
| menuClassName | Extra classes for the menu panel. |
| className | Merged onto the outer wrapper <div>. |
| … | Other native <div> attributes are forwarded (except children). |
Checkbox props (summary)
| Prop | Description |
| ----------------- | --------------------------------------------------------------------------- |
| label | Optional ReactNode. When set, wraps the input in a <label> so text is clickable and associated; label text is semibold while checked. |
| className | Merged with the component’s base classes |
| disabled | Native disabled behavior and muted appearance |
| checked / onChange | Controlled usage: drive checked from state and update in onChange. |
| defaultChecked | Uncontrolled default checked state |
| … | Other native <input> attributes are forwarded except type (always "checkbox"). Use aria-label when there is no visible label. |
CheckboxList props (summary)
| Prop | Description |
| ----------------- | --------------------------------------------------------------------------- |
| items | Array of { id, label, disabled? } — ids are used for selection and form value. |
| orientation | "column" (default) or "row" (horizontal with wrap). |
| value / onValueChange | Controlled multi-select: selected ids and updater. |
| defaultValue | Initial selected ids when uncontrolled. |
| legend | Optional <legend> for the fieldset; otherwise set aria-label on the list. |
| name | Optional shared name on each checkbox for HTML forms. |
| className | Merged onto the <fieldset>. |
| listClassName | Classes for the inner flex container. |
| … | Other native <fieldset> attributes are forwarded (except children / onChange). |
Navbar props (summary)
| Prop | Description |
| -------------- | --------------------------------------------------------------------------- |
| brand | Optional left cluster (logo, title, etc.). |
| children | Primary nav content; horizontal row between brand and trailing. |
| trailing | Optional right cluster (e.g. Button). |
| linksAlign | "left" (default), "center", or "right" — placement of children in the middle flex region. |
| sticky | When true, adds sticky top-0 z-50 on the root <nav>. |
| className | Merged onto the root <nav>. |
| … | Other native <nav> attributes are forwarded (for example aria-label, ref). |
Link props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| as | Optional React.ElementType; default "a". Use RouterLink or NavLink from react-router-dom for in-app navigation (to, etc.). |
| className | Merged with the component’s base classes. |
| aria-current | Set to "page" on the active route for current-page styling (or use NavLink, which sets this when active). |
| … | Other props are forwarded to the underlying element (href for <a>, to for router links, target, rel, etc.). |
Breadcrumbs props (summary)
| Prop | Description |
| ------------ | --------------------------------------------------------------------------- |
| items | Ordered path segments from root to current. Each item supports label, optional href, optional onClick, and optional ariaCurrent: "page" (defaults to current page for the last item). |
| separator | Separator between items (ReactNode). Defaults to /. |
| maxItems | When set and exceeded, collapses the middle behind an ellipsis. |
| renderItem | Optional custom render hook (item, { isCurrent }) => ReactNode. When provided, you control link/current styling and attributes. |
| className | Merged onto the root <nav>. |
| … | Other native <nav> attributes are forwarded (for example aria-label, ref). |
Toast props (summary)
| Prop | Description |
| -------------------- | --------------------------------------------------------------------------- |
| title | Short summary shown as the heading |
| type | "error", "warning", "info", or "success" |
| children | Optional body below the title |
| role | Optional "status" or "alert"; defaults by type (alert for error/warning) |
| onDismiss | When set, renders a dismiss button and calls this handler (you remove the toast from the UI) |
| dismissButtonProps | Optional props for the dismiss button (aria-label, className, etc.) |
| className | Merged onto the root |
| … | Other native <div> attributes are forwarded (except title / children) |
useClickAway (summary)
| Argument | Description |
| ---------- | --------------------------------------------------------------------------- |
| ref | Ref to the element; the handler runs when pointerdown occurs outside it |
| handler | (event: PointerEvent) => void |
| options | { enabled?: boolean } — when false, document listeners are not attached |
Development
Clone the repo, install dependencies, then:
| Command | Description |
| -------------------------- | -------------------------- |
| npm run build | Build dist/ (ESM + CJS + types) |
| npm test | Run tests |
| npm run storybook | Component docs / playground |
| npm run build-storybook | Static Storybook build |
See AGENTS.md for contribution conventions (structure, typing, tests, Storybook).
