# @andreagiugni/tailwind-dashboard-ui

> Reusable React + Tailwind CSS v4 dashboard components extracted from TailAdmin.

Latest version **2.0.1** (published 2026-10-15) · MIT license · 0 weekly downloads

## Install

```sh
npm install @andreagiugni/tailwind-dashboard-ui
pnpm add @andreagiugni/tailwind-dashboard-ui
yarn add @andreagiugni/tailwind-dashboard-ui
bun add @andreagiugni/tailwind-dashboard-ui
```

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 2.0.1 |
| Published | 2026-10-15 |
| First published | 2026-06-08 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | ESM |
| Dependencies | 4 |
| Unpacked size | 1.2 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Andrea Giugni |
| Maintainers | andregiugni |

## Links

- npm: https://www.npmjs.com/package/@andreagiugni/tailwind-dashboard-ui
- Homepage: https://taildashboardui.com
- npm.io page: https://npm.io/package/@andreagiugni/tailwind-dashboard-ui

## Dependencies (4)

- [clsx](https://npm.io/package/clsx.md) ^2.1.1
- [flatpickr](https://npm.io/package/flatpickr.md) ^4.6.13
- [tailwind-merge](https://npm.io/package/tailwind-merge.md) ^3.6.0
- [react-beautiful-color](https://npm.io/package/react-beautiful-color.md) ^2.1.0

## Recent versions

- 2.0.1 (latest) — 2026-10-15
- 2.0.0 — 2026-10-15
- 1.0.35 — 2026-10-13
- 1.0.34 — 2026-10-06
- 1.0.33 — 2026-09-30
- 1.0.32 — 2026-07-21
- 1.0.31 — 2026-07-09
- 1.0.30 — 2026-07-09
- 1.0.29 — 2026-07-07
- 1.0.28 — 2026-07-07
- 1.0.27 — 2026-07-02
- 1.0.26 — 2026-07-02
- 1.0.24 — 2026-07-02
- 1.0.23 — 2026-07-02
- 1.0.22 — 2026-07-01
- … 47 more at https://npm.io/package/@andreagiugni/tailwind-dashboard-ui/versions

## README

# @andreagiugni/tailwind-dashboard-ui

Reusable React + Tailwind CSS v4 dashboard components extracted from TailAdmin. Every primitive
ships with its Tailwind styling, extends the underlying element's native HTML attributes (so
`onClick`, `onDoubleClick`, `aria-*`, `data-*`, … just work), exposes named style variants, and —
where visually meaningful — accepts inline `bgColor` / `textColor` / `borderColor` overrides.

Dual ESM + CJS builds with TypeScript types. Core components are exported from the package root;
the optional-peer widgets use the dedicated `/charts`, `/calendar`, and `/editor` subpaths.

## Live preview

[![TailDashboardUI component library preview](https://taildashboardui.com/assets/taildashboardui-preview.png)](https://taildashboardui.com)

Explore every component with interactive previews, copy-ready snippets and complete prop
references at **[taildashboardui.com](https://taildashboardui.com)**.

## Install

```bash
npm i @andreagiugni/tailwind-dashboard-ui lucide-react
```

### Peer dependencies

The core package requires:

- **React 18 or 19** (`react`, `react-dom`)
- **Tailwind CSS v4** (`tailwindcss@^4`)
- **Lucide React** (`lucide-react@^1.21.0`) for the built-in component icons

`clsx`, `tailwind-merge`, and `flatpickr` (used by `DatePicker`) are bundled runtime dependencies —
you do not need to install them yourself.

The following heavy libraries are **optional** peers, only needed if you use the matching
data-viz component:

| Component | Optional peer deps |
|---|---|
| `BarChart`, `LineChart` | `apexcharts`, `react-apexcharts` |
| `Calendar` | `@fullcalendar/core`, `@fullcalendar/react`, `@fullcalendar/daygrid`, `@fullcalendar/timegrid`, `@fullcalendar/list`, `@fullcalendar/interaction` |
| `Editor` (WYSIWYG) | `@tiptap/react`, `@tiptap/pm`, `@tiptap/starter-kit`, `@tiptap/extension-link`, `@tiptap/extension-placeholder`, `@tiptap/extension-superscript`, `@tiptap/extension-subscript` |

Install only the ones for the components you use, e.g.:

```bash
npm i apexcharts react-apexcharts          # for BarChart / LineChart
npm i @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-link @tiptap/extension-placeholder @tiptap/extension-superscript @tiptap/extension-subscript  # for Editor
```

> **SSR note:** the data-viz components and the `Editor` touch `window`/the DOM at render time.
> In server-rendered frameworks (e.g. Next.js) render them client-only — for example with
> `next/dynamic(() => import('@andreagiugni/tailwind-dashboard-ui/charts').then(m => m.BarChart), { ssr: false })`.
> Import them from their dedicated subpaths, so optional peers are resolved only for the feature
> you import and the root package stays SSR-safe and light.

### `ColorPicker`

The `ColorPicker` is built on [`react-beautiful-color`](https://www.npmjs.com/package/react-beautiful-color),
which is a **bundled dependency** (installed automatically — you don't add it yourself). It is
client-only and code-split like the data-viz widgets, so render it client-only under SSR. It ships
its own stylesheet — import it once in your app:

```ts
import 'react-beautiful-color/dist/react-beautiful-color.css';
```

Pass `palette` to render reusable hex colors as selectable swatches below the hue and opacity sliders:

```tsx
<ColorPicker palette={["#465fff", "#12b76a", "#f79009", "#f04438"]} />
```

## Tailwind setup

The library ships its design tokens as an importable stylesheet. Add this line to your app's
Tailwind entry CSS (the file that already contains `@import 'tailwindcss';`):

```css
@import '@andreagiugni/tailwind-dashboard-ui/theme.css';
```

`theme.css` registers the full TailAdmin token set (brand / blue-light / gray / orange / success /
error / warning palettes, `--text-theme-*`, `--shadow-theme-*`, `--z-index-*`) inside an `@theme`
block, plus base layer rules.

**Dark mode** is driven by the `.dark` class. The theme defines a custom `dark` variant against an
ancestor `.dark`, so toggle dark mode by adding/removing the `dark` class on `<html>` (or any
ancestor):

```html
<html class="dark">
```

The `ThemeToggleButton` component is controllable for this purpose (`theme` + `onToggle` props).

## Importing components

Core components are exported from the package root:

```tsx
import {
  Button,
  Badge,
  Alert,
  Input,
  Select,
  Modal,
  Table,
  TableHeader,
  TableBody,
  TableRow,
  TableCell,
  // hero-ui parity additions
  Accordion,
  AccordionItem,
  Tabs,
  Tab,
  Tooltip,
  Popover,
  Drawer,
  Chip,
  Progress,
  PollResults,
  Spinner,
  Skeleton,
  Slider,
  Code,
  Snippet,
} from '@andreagiugni/tailwind-dashboard-ui';
```

Heavy optional-peer components use dedicated subpaths:

```tsx
import { BarChart, LineChart } from '@andreagiugni/tailwind-dashboard-ui/charts';
import { Calendar } from '@andreagiugni/tailwind-dashboard-ui/calendar';
import { Editor } from '@andreagiugni/tailwind-dashboard-ui/editor';
```

## Props model

Three conventions apply across the library:

1. **Native handlers via HTML-attribute extension.** Each component's props extend the root
   element's React HTML attribute type (e.g. `ButtonHTMLAttributes`), so every native handler and
   attribute (`onClick`, `onDoubleClick`, `onMouseEnter`, `aria-*`, `data-*`, …) is available with
   zero extra wiring and is forwarded onto the underlying element.
2. **Named variants.** Visual states are selected with discrete props such as `variant`, `size`,
   and `color` instead of class strings.
3. **Inline color overrides.** Where meaningful, components accept `bgColor`, `textColor`, and
   `borderColor`. These map to inline `style` (`backgroundColor` / `color` / `borderColor`) and let
   you escape the named palette for one-off colors. A consumer-supplied `style` prop always wins.

```tsx
import { Button, Badge } from '@andreagiugni/tailwind-dashboard-ui';
import '@andreagiugni/tailwind-dashboard-ui/theme.css'; // in your Tailwind entry CSS

<Button variant="primary" onDoubleClick={handle}>Save</Button>
<Button bgColor="#ff5722" textColor="#fff">Custom</Button>
<Badge color="success">Active</Badge>
```

## Component reference

Legend: **(+native)** = also extends the element's native HTML attributes.
**(+override)** = accepts `bgColor` / `textColor` / `borderColor`.

### Core UI

| Component | Key props |
|---|---|
| `Button` | `variant` (primary/outline), `size` (sm/md), `startIcon`, `endIcon`, `disabled`, (+native button) (+override) |
| `Badge` | `variant` (light/solid), `color` (primary/success/error/warning/info/light/dark), `size` (sm/md), `startIcon`, `endIcon`, (+native span) (+override) |
| `Ribbon` | `variant` (corner/rounded/flag), `color` (primary/success/error/warning/info/dark), `position` (top-left/top-right/bottom-left/bottom-right), `children` (label), (+native span) (+override) — place inside a `relative` host (corner needs `overflow-hidden`) |
| `Alert` | `variant` (success/error/warning/info), `title`, `message`, `showLink`, `linkHref`, `linkText`, `icon?` (override icon), (+native div) |
| `Avatar` | `src`, `alt`, `size` (xsmall…xxlarge), `status` (online/offline/busy/none), `statusColor?`, (+native img) |
| `AvatarText` | `children`, `className`, (+native div) |
| `Dropdown` | `isOpen`, `onClose`, `children`, `portal?` (default `true`, prevents clipping inside cards/tables/overflow containers), `triggerRef?` (optional custom trigger anchor), (+native div) |
| `DropdownItem` | `variant` (default/primary/outline, like Button), `icon?`, `bgColor`/`textColor`/`borderColor` overrides, `tag` (a/button), `href`, `onClick`, `onItemClick`, `baseClassName` (full override) |
| `Modal` | `isOpen`, `onClose`, `title?` (wrapping header aligned with close button), `showCloseButton`, `isFullscreen`, `closeOnBackdrop?`, `closeOnEsc?`; **alert preset** → `variant` (`confirm`/`error`/`warning`) + animated SVG icon, `title`, `description`, `actionText?`, `onAction?`; (+native div) |
| `Table` (composable) | `TableHeader` / `TableBody` / `TableRow` / `TableCell` with `children`, `isHeader` (cell), (+native table elements; `onClick` / `onDoubleClick` on rows & cells) |
| `Table` (data-driven) | pass `data` + `columns` (`key`, `header`, `sortable?`, `align?`, `render?`, `className?`) to get search + three-state per-column sort (neutral/ascending/descending) + pagination: `rowsPerPage?` (max rows/page, default 10), `rowsPerPageOptions?`, `searchKeys?` (**search box shown only when provided**), `searchPlaceholder?`, `defaultSortKey?` / `defaultSortDirection?`, `pagination?`, `paginationAlign?` (`left`/`center`/`right`/`full`, default `right`), `showSizeSelector?`, `onRowClick?`, `getRowId?`, `emptyContent?` |
| `Pagination` | `currentPage`, `totalPages`, `onPageChange`, `align?` (`left`/`center`/`right`/`full` — `full` pins Previous/Next to the edges with page indices centered) |
| `Breadcrumb` | `pageTitle`, `items?`, (+native) |
| `ThemeToggleButton` | `theme?` (controlled), `onToggle?`, (+native button) |
| `Card` | `title?`, `description?`, `icon?`, `image?`, `imageAlt?`, `horizontal?`, `footer?`, `children?`, (+native div) (+override) |
| `Accordion` / `AccordionItem` | `selectionMode` (single/multiple), `expandedKeys` / `defaultExpandedKeys`, `onExpandedChange`; item: `itemKey`, `title`, `startContent?`, `disabled` |
| `Tabs` / `Tab` | `variant` (underline/solid/pill), `selectedKey` / `defaultSelectedKey`, `onSelectionChange`, `color?`; tab: `tabKey`, `title`, `disabled` |
| `Chip` | `color`, `variant` (light/solid), `size`, `startIcon`, `endIcon`, `avatar?`, `onClose?`, (+native span) (+override) |
| `Code` | `color` (primary/success/error/warning/info/default), `size` (sm/md), (+native code) (+override) |
| `Snippet` | `code` (string \| string[]), `symbol?`, `hideCopyButton?`, `hideSymbol?`, `size`, (+override) |
| `Progress` | `value`, `maxValue?`, `color`, `size` (sm/md/lg), `label?`, `showValueLabel?`, `isIndeterminate?` |
| `PollResults` | `options` (`{ label, value?, series? }[]`), `maxValue?`, `size` (sm/md/lg), `color`, `trackColor?`, `showValueLabel?`, `showSeriesLabels?`, `valueFormatter?`, `labelTextSize?`/`labelTextColor?`/`labelTextWeight?`, `valueTextSize?`/`valueTextColor?`/`valueTextWeight?`, `seriesLabelTextSize?`/`seriesLabelTextColor?`/`seriesLabelTextWeight?`, `seriesValueTextSize?`/`seriesValueTextColor?`/`seriesValueTextWeight?`, (+native div) (+override) |
| `Spinner` | `size` (sm/md/lg), `color`, `label?` |
| `Skeleton` | `isLoaded`, `width?`, `height?`, `className`, `children?` |
| `Toast` | `variant` (success/danger/warning/info), `position?` (left/center/right), `duration?` (ms), `title`, `message?`, `icon?`, `hideIcon?`, `showCloseButton?`, `onClose?`, `hideAccentBar?`, (+native div) (+override) |

### Overlays

| Component | Key props |
|---|---|
| `Tooltip` | `content`, `placement` (top/right/bottom/left), `delay?`, `disabled?`, (+native span) (+override) |
| `Popover` | `trigger`, `placement`, `isOpen?` / `defaultOpen?` / `onOpenChange?`, `closeOnOutsideClick?`, `closeOnEsc?`, `portal?` (default `true`, prevents clipping inside cards/overflow containers) |
| `Drawer` | `isOpen`, `onClose`, `placement` (left/right/top/bottom), `showCloseButton?`, `closeOnBackdrop?`, `closeOnEsc?`, `size?`, (+native div) |

### Form controls

| Component | Key props |
|---|---|
| `Input` | `type`, `value` / `defaultValue`, `onChange`, `success`, `error`, `hint`, `disabled`, (+native input) (+override) |
| `Label` | `htmlFor`, `children`, (+native label) |
| `Select` | `options` (`{ value, label, icon? }[]`), `placeholder`, `defaultValue`, `onChange(value)`, `disabled`, `searchable?` (default `false`, filters labels with a search input), `portal?` (default `true`, prevents clipping inside tables/cards). Without search, typing a prefix scrolls to a matching option; repeated letters cycle matches. Arrow keys navigate, Enter selects, Escape cancels. |
| `MultiSelect` | `options`, `selected` / `defaultSelected`, `onChange`, `placeholder`, `disabled`, `portal?` (default `true`, prevents clipping inside tables/cards) |
| `Checkbox` | `label`, `checked`, `onChange(checked)`, `disabled`, `checkedColor?` |
| `Radio` / `RadioSm` | `id`, `name`, `value`, `checked`, `label`, `onChange(value)`, `disabled` |
| `Switch` | `label?`, `checked` / `defaultChecked`, `onChange(checked)`, `color` (blue/gray), `disabled` |
| `TextArea` | `value`, `onChange`, `rows`, `success`, `error`, `hint`, `disabled` |
| `FileInput` | `onChange`, `accept`, `multiple`, (+native input) |
| `PasswordInput` | password field with a show/hide eye toggle, (+native input attrs) |
| `Dropzone` | `onDrop(files)`, `accept?`, `multiple?`, `title?`, `description?`, `browseLabel?` — drag-and-drop upload, dependency-free |
| `DatePicker` | `id`, `mode` (single/multiple/range/time), `defaultDate`, `onChange`, `label`, `placeholder` — calendar is appended to `document.body` to avoid clipping inside cards/overflow containers |
| `DateTimePicker` | `id`, `label?`, `placeholder?`, `defaultDate?`, `defaultTime?` (`"HH:MM"`), `onChange?({ date, time })` — flatpickr date calendar appended to `document.body` + a compact `HH:MM` time input |
| `Slider` | `value` / `defaultValue`, `onChange(value)`, `min`, `max`, `step`, `color`, `label?`, `showValue?`, `disabled` |
| `ColorPicker` | `defaultValue?` (hex), `defaultFormat?` / `formats?` (`hex`/`rgb`/`rgba`/`hsl`/`hsla`), `showInput?`, `withEyeDropper?`, `palette?` (hex colors), `label?`, `onChange?(value, color)` — compact swatch + formatted value trigger opening a saturation/hue/alpha popup with selectable palette colors, value input, format switcher and clear-value button; built on `react-beautiful-color` (bundled), client-only, **import its CSS** (see ColorPicker note above) |
| `Editor` (WYSIWYG) | `content`, `onChange(html)`, `placeholder?`, `editable?`, `toolbar?` (ordered tool ids or custom buttons), `rows?` (minimum visible text rows, default `8`), `editorClassName?` — optional Tiptap peers; render client-only (see Data viz) |

### Data viz

These wrap optional heavy peer deps and must be rendered client-only under SSR (see the SSR note
in [Peer dependencies](#peer-dependencies)).

| Component | Key props |
|---|---|
| `BarChart` | `series`, `categories`, `colors?`, `barColors?` (per-option bar colors), `height?`, `horizontal?`, `showBarValues?`, `showBarPercentages?`, `minBarWidth?` (fixed px minimum for horizontal bars, default `30`), `percentageMode?`, `barValueFormatter?`, `barPercentageFormatter?`, `options?` (merged over defaults) |
| `LineChart` | `series`, `categories`, `colors?`, `height?`, `options?` |
| `Calendar` | `events`, `onEventClick`, `onDateSelect`, `initialView` |
| `Editor` (WYSIWYG) | `content`, `onChange(html)`, `placeholder?`, `editable?`, `toolbar?`, `rows?` — Tiptap-powered, customizable toolbar |

> The data-viz components default their data to sensible demo values, so they render
> out-of-the-box and are fully overridable via the props above.

The `Editor` is a Tiptap-powered rich-text editor with a fully customizable toolbar. Pass an
ordered `toolbar` array of built-in tool ids (`"bold" | "italic" | "strike" | "code" |
"superscript" | "subscript" | "heading1" | "heading2" | "heading3" | "bulletList" |
"orderedList" | "blockquote" | "codeBlock" | "link" | "undo" | "redo" | "divider"`) or custom `{ id, icon, title, onClick,
isActive? }` button objects. Set `editable={false}` for a read-only view (toolbar hidden).
Use `rows` to control the minimum visible text rows and therefore the editor content height.

```tsx
import dynamic from 'next/dynamic';
const Editor = dynamic(
  () => import('@andreagiugni/tailwind-dashboard-ui/editor').then((m) => m.Editor),
  { ssr: false }
);

<Editor
  content="<p>Hello <strong>world</strong></p>"
  onChange={(html) => save(html)}
  toolbar={['bold', 'italic', 'divider', 'bulletList', 'orderedList', 'link']}
  rows={10}
/>
```

## Table

`Table` has **two modes**, both under the same component:

1. **Composable** — use `Table` / `TableHeader` / `TableBody` / `TableRow` / `TableCell` as thin,
   styled wrappers around the native table elements when you want full control over the markup.
2. **Data-driven** — pass a `data` array and a `columns` definition and you get live search,
   per-column click-to-sort, a "Show N entries" selector and pagination out of the box. The mode
   switches on automatically as soon as `columns` is provided.

```tsx
import { Table, type TableColumn } from '@andreagiugni/tailwind-dashboard-ui';

type Person = { name: string; office: string; salary: number };

const people: Person[] = [
  { name: 'Abram Schleifer', office: 'Edinburgh', salary: 89500 },
  { name: 'Carla George', office: 'London', salary: 15500 },
  // …
];

const columns: TableColumn<Person>[] = [
  { key: 'name', header: 'User', sortable: true },
  { key: 'office', header: 'Office', sortable: true },
  {
    key: 'salary',
    header: 'Salary',
    sortable: true,
    align: 'right',
    render: (row) => `$${row.salary.toLocaleString()}`, // custom cell
  },
];

<Table
  data={people}
  columns={columns}
  rowsPerPage={5}              // max rows per page (default 10)
  rowsPerPageOptions={[5, 10, 25]}
  defaultSortKey="name"
  searchKeys={['name', 'office']} // omit to hide the search box
  onRowClick={(row) => console.log(row)}
/>
```

Key data-driven props: `data`, `columns` (each: `key`, `header`, `sortable?`, `align?`, `render?`,
`className?`), `rowsPerPage` (default 10), `rowsPerPageOptions` (default `[5,10,25,50]`),
`searchKeys` / `searchPlaceholder`, `defaultSortKey` / `defaultSortDirection`, `pagination`,
`paginationAlign` (`left` / `center` / `right` / `full`, default `right`), `showSizeSelector`,
`onRowClick`, `getRowId`, `emptyContent`.

> **Search visibility:** the search input renders **only** when you pass a non-empty `searchKeys`
> array — those are also the exact fields the query matches. Omit `searchKeys` (or pass `[]`) to
> hide search entirely.

## License

MIT © Andrea Giugni

---
_Source: https://npm.io/package/@andreagiugni/tailwind-dashboard-ui · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
