# PrimeReact # LLMs.txt LLMs.txt is a file that contains the LLMs for the PrimeReact documentation. ## /llms.txt The [llms.txt](https://llmstxt.org/) file is an industry standard that helps AI models better understand and navigate the PrimeReact documentation. It lists key pages in a structured format, making it easier for LLMs to retrieve relevant information.   ## /llms-full.txt The `llms-full.txt` file is a complete list of all the pages in the PrimeReact documentation. It is used to help AI models understand the entire documentation set.   ## .md extension Add a `.md` to a page’s URL to display a Markdown version of that page. # LLMs.txt LLMs.txt is a file that contains the LLMs for the PrimeReact documentation. ## /llms.txt The [llms.txt](https://llmstxt.org/) file is an industry standard that helps AI models better understand and navigate the PrimeReact documentation. It lists key pages in a structured format, making it easier for LLMs to retrieve relevant information.   ## /llms-full.txt The `llms-full.txt` file is a complete list of all the pages in the PrimeReact documentation. It is used to help AI models understand the entire documentation set.   ## .md extension Add a `.md` to a page’s URL to display a Markdown version of that page. # LLMs.txt LLMs.txt is a file that contains the LLMs for the PrimeReact documentation. ## /llms.txt The [llms.txt](https://llmstxt.org/) file is an industry standard that helps AI models better understand and navigate the PrimeReact documentation. It lists key pages in a structured format, making it easier for LLMs to retrieve relevant information.   ## /llms-full.txt The `llms-full.txt` file is a complete list of all the pages in the PrimeReact documentation. It is used to help AI models understand the entire documentation set.   ## .md extension Add a `.md` to a page’s URL to display a Markdown version of that page. # Add-Ons Premium templates, UI blocks and design tooling that build on top of PrimeReact. ## Overview Add-Ons are premium, production-ready resources built on top of PrimeReact. They help you skip the boilerplate and ship faster, from full application templates to ready-made UI blocks and design tooling. ## What's included - **Templates**, complete, themeable application starters with layouts, pages and integrations wired up. - **Blocks**, copy-and-paste UI sections for application, marketing and e-commerce pages. - **Figma UI Kit**, the design source that mirrors the component library, for designing before you build. - **Theme Designer**, a visual tool to craft and export your own design tokens and themes. Pick a category from the sidebar to explore each add-on. # Installation Install PrimeReact's headless hooks and build fully custom components in your React project. Start from our Next.js template, pre-configured with PrimeReact. Headless hooks give you the behavior — state, keyboard navigation, focus and accessibility — while you own the markup and styles. They work in any React setup; the steps below show a Next.js example you can adapt to your own framework. ### Install Packages Install the headless package using your favorite package manager: ```bash npm install @primereact/headless ``` ### PrimeReactProvider Wrap your app with `PrimeReactProvider` so the hooks' shared context is available everywhere. No theme or preset is needed — you style everything yourself: ```tsx showLineNumbers title="components/prime-provider.tsx" 'use client'; import { PrimeReactProvider } from '@primereact/core'; export function PrimeProvider({ children }: { children: React.ReactNode }) { return {children}; } ``` Then wrap your app with it in `app/layout.tsx`: ```tsx showLineNumbers title="app/layout.tsx" {1,6} import { PrimeProvider } from '@/components/prime-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Add a `cn` Helper (optional) If you style with Tailwind, a `cn` helper makes merging class names easier. Install `tailwind-merge` and create `lib/utils.ts`: ```bash npm install tailwind-merge ``` ```ts showLineNumbers title="lib/utils.ts" import { cn as _cn } from '@primeuix/utils'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: unknown[]) { return twMerge(_cn(...inputs)); } ``` ### Verify Build a component with a hook and style it yourself — the hook wires up state, keyboard, focus and accessibility: ```tsx 'use client'; import { useButton } from '@primereact/headless/button'; import { cn } from '@primeuix/utils'; export default function VerifyDemo() { const { rootProps } = useButton(); return (
); } ``` ```tsx showLineNumbers {2,6} 'use client'; import { useButton } from '@primereact/headless/button'; import { cn } from '@/lib/utils'; export default function VerifyInstallation() { const { rootProps } = useButton(); return ( ); } ```
## Next Steps [Browse the hooks](/docs/headless/components/autocomplete) and compose the behavior you need into your own markup and styles. # Headless React hooks that handle logic and accessibility. You bring your own markup and styles. ## Overview Headless mode exposes each component as a hook, like `useDialog`, `useSelect`, or `useAutoComplete`. Hooks return props you spread onto your own elements. You get state management, keyboard interactions, focus handling, and ARIA wiring. You write the JSX and CSS. Use headless mode when you want maximum control over markup and styling, or when you're building a custom component library on top of our logic. ## What you get - 70+ hooks covering every interaction pattern - Zero opinion on markup or styling - Full TypeScript inference on returned props - The foundation for our Styled, Tailwind, and Primitive layers ## Next steps Browse the hooks in the sidebar or check [Getting Started](/docs/overview/gettingstarted/introduction) for setup. # Resources Support channels, legacy versions and places to discover more about PrimeReact. ## Overview Everything around the library, where to get help, find older versions, and stay up to date. ## What's here - **Support**, get help on Discord, the community forum, or through premium support. - **Legacy**, documentation for previous major versions. - **Discover**, the store and social channels. Pick a category from the sidebar to jump in. # Add-Ons Premium templates, UI blocks and design tooling that build on top of PrimeReact. ## Overview Add-Ons are premium, production-ready resources built on top of PrimeReact. They help you skip the boilerplate and ship faster, from full application templates to ready-made UI blocks and design tooling. ## What's included - **Templates**, complete, themeable application starters with layouts, pages and integrations wired up. - **Blocks**, copy-and-paste UI sections for application, marketing and e-commerce pages. - **Figma UI Kit**, the design source that mirrors the component library, for designing before you build. - **Theme Designer**, a visual tool to craft and export your own design tokens and themes. Pick a category from the sidebar to explore each add-on. # Installation Install PrimeReact and compose unstyled primitive components in your React project. Start from our Next.js template, pre-configured with PrimeReact. Primitive components ship **unstyled** — you compose the behavior and bring your own styles. They work in any React setup; the steps below show a Next.js example you can adapt to your own framework. ### Install Packages Install PrimeReact using your favorite package manager: ```bash npm install primereact ``` ### PrimeReactProvider Wrap your app with `PrimeReactProvider` so the primitives' shared context is available everywhere. No theme or preset is needed — you style everything yourself: ```tsx showLineNumbers title="components/prime-provider.tsx" 'use client'; import { PrimeReactProvider } from '@primereact/core'; export function PrimeProvider({ children }: { children: React.ReactNode }) { return {children}; } ``` Then wrap your app with it in `app/layout.tsx`: ```tsx showLineNumbers title="app/layout.tsx" {1,6} import { PrimeProvider } from '@/components/prime-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Add a `cn` Helper (optional) If you style with Tailwind, a `cn` helper makes merging class names easier. Install `tailwind-merge` and create `lib/utils.ts`: ```bash npm install tailwind-merge ``` ```ts showLineNumbers title="lib/utils.ts" import { cn as _cn } from '@primeuix/utils'; import { twMerge } from 'tailwind-merge'; export function cn(...inputs: unknown[]) { return twMerge(_cn(...inputs)); } ``` ### Verify Render the unstyled `Button` primitive, styled with your `cn` helper, to confirm everything is wired up: ```tsx import { cn } from '@primeuix/utils'; import { Button } from 'primereact/button'; export default function VerifyDemo() { return (
); } ``` ```tsx showLineNumbers {1} import { Button } from 'primereact/button'; import { cn } from '@/lib/utils'; export default function VerifyInstallation() { return ; } ```
## Next Steps [Browse the components](/docs/primitive/components/autocomplete) and compose the unstyled primitives you need, styling them with any CSS solution. # Primitive Unstyled React components with composable subcomponents. Behavior included, styling up to you. ## Overview Primitives are low-level React components built on top of our headless hooks. Unlike hooks, they provide a compound component API, `Dialog.Root`, `Dialog.Trigger`, `Dialog.Content`, so you compose pieces without wiring up hook props manually. Use primitives when you want the ergonomics of a component API but full freedom over styles. They're what our Styled and Tailwind layers are built on. ## What you get - 70+ components with a compound API - No styles, use any CSS solution you like - Data attributes for state-based styling (`data-state`, `data-disabled`) - Full accessibility and keyboard navigation ## Next steps Open any component in the sidebar to see its subcomponents and composition examples. # Resources Support channels, legacy versions and places to discover more about PrimeReact. ## Overview Everything around the library, where to get help, find older versions, and stay up to date. ## What's here - **Support**, get help on Discord, the community forum, or through premium support. - **Legacy**, documentation for previous major versions. - **Discover**, the store and social channels. Pick a category from the sidebar to jump in. # Introduction A standalone collection of React hooks from @primereact/hooks. ## Overview `@primereact/hooks` is a standalone library of React hooks. Each hook solves one concrete problem, filtering a list, formatting a number, watching a media query, reacting to a resize, and none of them render DOM or depend on PrimeReact components. You can use it in any React project. It is the same package PrimeReact itself builds on, published separately so you can reach for a single hook without pulling in a component library. ## What you get - **No framework lock-in.** The hooks work with any React setup, CRA, Vite, Next.js, Remix, React Native Web. - **Tree-shakeable.** Every hook has its own entry point; bundlers only ship what you import. - **Typed.** Full TypeScript declarations. Generic hooks pick up your data type automatically. - **SSR-safe.** Hooks that touch the DOM are marked `'use client'` internally, importing from a server component is fine. ## Conventions A few things are true across the suite: - Stateful hooks support controlled and uncontrolled usage. Pass `value` + `onChange` to control, or start from a `default*` prop and let the hook own the state. - Options are passed as a single object. Return shapes are destructurable, pick what you need. - Hook names follow the `useX` convention and ship from `@primereact/hooks/use-x`. ```tsx import { useFilter } from '@primereact/hooks/use-filter'; const { filteredData, setValue } = useFilter({ data: customers, field: 'name', value: query }); ``` # Setup Install @primereact/hooks and start using it in any React project. ## Install `@primereact/hooks` is a standalone package. Install it on its own: ```bash npm install @primereact/hooks ``` If you already use `primereact`, the hooks are bundled, you don't need a separate install, but you can still depend on `@primereact/hooks` directly if you prefer explicit versioning. Peer dependency: **React 18+**. ## Import Each hook has its own sub-path entry for optimal tree-shaking: ```tsx import { useFilter } from '@primereact/hooks/use-filter'; import { useLocalStorage } from '@primereact/hooks/use-local-storage'; import { useMatchMedia } from '@primereact/hooks/use-match-media'; ``` The root entry re-exports everything when convenience matters more than the last few bytes: ```tsx import { useFilter, useLocalStorage } from '@primereact/hooks'; ``` ## TypeScript Types ship with the package. Generic hooks infer from the data you pass in: ```tsx interface Customer { id: number; name: string; country: string; } const { filteredData } = useFilter({ data: customers, field: 'name', value: query }); // filteredData is Customer[] ``` ## SSR / Next.js Hooks can be imported from server components without extra setup. Any file that touches the DOM is marked `'use client'` internally, so it moves to the client boundary on demand. # useAttrSelector Produce a stable, unique string from React.useId, handy for scoping CSS and DOM selectors. ## Usage ```tsx import { useAttrSelector } from '@primereact/hooks/use-attr-selector'; ``` ```tsx const selector = useAttrSelector('panel-'); // e.g. "panel-r0" ``` Pass an optional prefix; the hook strips colons and other delimiters React adds to `useId()` so the result is safe to use inside attribute selectors like `[data-scope="panel-r0"]`. ## Signature ```ts function useAttrSelector(prefix?: string): string; ``` ## When to use it Reach for this when you need a unique string that also works as a CSS selector fragment, scoped styles, data attributes, or query targets inside a single component instance. # useControlledState State that works controlled or uncontrolled, following the React convention of value + onChange with a defaultValue fallback. ## Usage ```tsx import { useControlledState } from '@primereact/hooks/use-controlled-state'; ``` ```tsx const [value, setValue, isControlled] = useControlledState({ value: props.value, defaultValue: props.defaultValue, onChange: props.onChange }); ``` When the parent passes `value`, the hook stays controlled and delegates every update through `onChange`. When `value` is `undefined`, it owns the state internally, `defaultValue` seeds the first render. ## Signature ```ts function useControlledState(options: { value?: T; defaultValue?: T; onChange?: (next: E) => void }): [T | undefined, (next: T | ((prev?: T) => T)) => void, boolean]; ``` The returned tuple is `[value, setValue, isControlled]`. `isControlled` is a boolean frozen at mount, matching how React decides controlled vs uncontrolled inputs. ## When to use it Any reusable component that exposes both `value` / `onChange` and a `defaultValue`. This hook handles the glue so you don't branch on `value === undefined` everywhere. # useEventListener Attach DOM event listeners declaratively, with automatic cleanup and a bind/unbind pair for manual control. ## Usage ```tsx import { useEventListener } from '@primereact/hooks/use-event-listener'; ``` ```tsx const [bind, unbind] = useEventListener({ target: 'document', type: 'click', listener: (event) => console.log(event), when: true }); ``` The listener is attached when `when` is `true` and torn down automatically on unmount. Toggle `when` to pause without unmounting. ## Options - `target`, `'document' | 'window' | 'body' | HTMLElement | (() => HTMLElement) | Ref | string | null`. Accepts a selector string, a ref, or a function that resolves the element on demand. - `type`, event name, e.g. `'click'`, `'keydown'`, or a custom event. - `listener`, handler function. - `options?`, standard `addEventListener` options (`capture`, `passive`, `once`). - `when?`, defaults to `true`; set `false` to disable. ## Manual control The returned `[bind, unbind]` lets you attach/detach imperatively, useful when you want to listen in response to a user action rather than a prop: ```tsx ``` # useFilter Generic filter hook for flat collections, with built-in match modes, accent-insensitive matching, locale support, and custom matcher registration. ## Usage ```tsx import { useFilter } from '@primereact/hooks/use-filter'; ``` ```tsx const { filteredData, value, setValue, isFiltered } = useFilter({ data: products, field: ['name', 'category'], matchMode: 'contains' }); ``` `useFilter` is a framework-level hook from `@primereact/hooks` that takes arbitrary data plus one or more filter rules and returns the filtered result. The same built-in matcher registry powers DataTable's filter feature, so behaviour stays consistent across any component built on top of it. ## Signature ```ts function useFilter(options: UseFilterOptions): UseFilterReturn; ``` ## Rules A rule targets one field (or an array of fields for multi-field search) with a value and a match mode. ```tsx defaultRules: [ { field: 'name', value: 'iphone', matchMode: 'startsWith' }, { field: ['name', 'category', 'description'], value: 'blue' }, { field: 'price', value: [100, 500], matchMode: 'between' } ]; ``` Composite rules carry multiple constraints tied by an AND/OR operator: ```tsx { field: 'name', operator: 'and', constraints: [ { value: 'A', matchMode: 'startsWith' }, { value: 'Z', matchMode: 'notEquals' } ] } ``` ## Basic For the common "single field/multi-field search" case you can skip the `rules` array and use the `field` / `value` / `matchMode` shorthand. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Search } from '@primeicons/react/search'; import { useFilter } from '@primereact/hooks/use-filter'; import { IconField } from '@primereact/ui/iconfield'; import { InputText } from '@primereact/ui/inputtext'; import { Tag } from '@primereact/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; export default function BasicDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 12)); }, []); const [search, setSearch] = React.useState(''); const { filteredData } = useFilter({ data: products, field: ['name', 'category', 'code'], value: search, matchMode: 'contains' }); return (
) => setSearch(e.target.value)} className="w-full" />
{filteredData.map((item) => (
{item.name}
{item.name} {item.code}
{item.category} {item.inventoryStatus}
))}
{filteredData.length === 0 && (
No products match the search.
)}
); } ``` ## Advanced, multi-rule + custom matcher Pass `matchers` to register an ad-hoc matcher for a single hook instance. The cursor API (`rule(index)`) gives a typed handle per rule. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { ChevronDown } from '@primeicons/react/chevron-down'; import { Search } from '@primeicons/react/search'; import { useFilter, type FilterMatcher } from '@primereact/hooks/use-filter'; import { Button } from '@primereact/ui/button'; import { IconField } from '@primereact/ui/iconfield'; import { InputText } from '@primereact/ui/inputtext'; import { Select, type SelectValueChangeEvent } from '@primereact/ui/select'; import { Tag } from '@primereact/ui/tag'; import * as React from 'react'; const statusOptions = [ { label: 'All statuses', value: '' }, { label: 'In Stock', value: 'INSTOCK' }, { label: 'Low Stock', value: 'LOWSTOCK' }, { label: 'Out of Stock', value: 'OUTOFSTOCK' } ]; const extraMatchers: Record = { expensive: (value) => Number(value) >= 100 }; export default function AdvancedDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 12)); }, []); const { filteredData, rule, isFiltered, clearAll } = useFilter({ data: products, defaultRules: [ { field: 'name', value: null, matchMode: 'contains' }, { field: 'inventoryStatus', value: null, matchMode: 'equals' }, { field: 'price', value: null, matchMode: 'expensive' } ], matchers: extraMatchers }); const nameRule = rule(0); const statusRule = rule(1); const priceRule = rule(2); return (
) => nameRule?.setValue(e.target.value)} className="w-full" /> statusRule?.setValue(e.value || null)} options={statusOptions} optionLabel="label" optionValue="value" size="small" > {isFiltered && ( )}
{filteredData.map((item) => (
{item.name} {item.code}
{item.category} ${item.price}
))}
{filteredData.length === 0 && (
No products match the filters.
)}
); } ``` ## Global matcher registry For matchers you want available everywhere, `registerMatcher` writes into a shared table the hook consults alongside the built-ins. ```tsx import { registerMatcher } from '@primereact/hooks/use-filter'; registerMatcher('greaterThanAvg', (value, filter) => Number(value) > Number(filter)); ``` Local `matchers` option always wins when both are present. Call `unregisterMatcher(name)` to remove an entry (useful in tests). ## Lazy (server-side) Set `lazy: true` to skip client-side filtering. `filteredData` is returned unchanged. Whenever the rules settle (respecting `filterDelay`), `onLazyLoad` fires with the current rule set, typically you turn that into a request and replace `data` with the server response. ```tsx const [data, setData] = React.useState([]); const [query, setQuery] = React.useState(''); useFilter({ data, lazy: true, field: ['name', 'country'], value: query, onValueChange: (v) => setQuery((v as string) ?? ''), filterDelay: 300, onLazyLoad: async ({ rules }) => { const res = await fetch('/api/customers?' + toQuery(rules)); setData(await res.json()); } }); ``` ## Cursor API Setters by index: ```tsx const { setValue, setMatchMode, setOperator, setConstraintValue } = useFilter({ ... }); setValue(0, 'amy'); setConstraintValue(0, 1, 'Z'); ``` Or scoped by rule: ```tsx const { rule } = useFilter({ ... }); rule(0)?.setValue('amy'); rule('name')?.addConstraint('equals'); rule('name')?.constraint(1)?.setValue('Z'); ``` # useHotKey Bind keyboard shortcuts with modifier combinations, single key or array of keys, with enable flag for scoping. ## Usage ```tsx import { useHotKey } from '@primereact/hooks/use-hot-key'; ``` ```tsx useHotKey('ctrl+k', (event) => { event.preventDefault(); openCommandPalette(); }); ``` Modifiers are case-insensitive and accept aliases: `ctrl`/`control`, `cmd`/`meta`/`command`, `alt`/`option`, `shift`. The special strings `space` and `spacebar` resolve to the space key. Pass an array of specs to bind multiple shortcuts to the same handler: ```tsx useHotKey(['ctrl+k', 'meta+k'], openCommandPalette); ``` ## Signature ```ts function useHotKey( keys: string | string[], handler: (event: KeyboardEvent) => void, options?: { target?: Document | HTMLElement | null; event?: 'keydown' | 'keyup' | 'keypress'; when?: boolean; } ): void; ``` Defaults: listens on `document` for `keydown` while `when` is true. Pass a target element to scope shortcuts to a specific region. # useId Stable unique identifier with an optional explicit override. ## Usage ```tsx import { useId } from '@primereact/hooks/use-id'; ``` ```tsx const id = useId(); // e.g. "pr_id_r0" const fixed = useId('my-panel'); // "my-panel" ``` Pass a value to opt out of the generated id, useful for components that accept an optional `id` prop but need a fallback. ## Signature ```ts function useId(initialValue?: string): string; ``` Internally wraps `React.useId()` and strips the characters React inserts (`:`, `«`, `»`) so the result is safe to use in selectors and HTML attributes. # useIntersectionObserver A thin wrapper over the IntersectionObserver API with imperative observe/unobserve controls. ## Usage ```tsx import { useIntersectionObserver } from '@primereact/hooks/use-intersection-observer'; ``` ```tsx const ref = React.useRef(null); const { observe, isIntersecting } = useIntersectionObserver({ threshold: 0.5, onIntersect: (entries) => console.log(entries) }); React.useEffect(() => { if (ref.current) observe(ref.current); }, [observe]); return
{isIntersecting ? 'visible' : 'off-screen'}
; ``` ## Options Standard `IntersectionObserverInit` fields (`root`, `rootMargin`, `threshold`) plus: - `disabled?`, skip creating the observer entirely. - `onIntersect?(entries, observer)`, called on every intersection change. - `fallback?()`, invoked when `IntersectionObserver` is unavailable. ## Returns - `entries`, current entries from the last callback. - `isIntersecting`, `true` if any observed element is intersecting. - `observe(el)`, `unobserve(el)`, `disconnect()`, imperative controls. - `observer`, the underlying `IntersectionObserver` instance (or `null` when disabled). ## When to use it Pick this hook when you need direct control, observing multiple elements, toggling dynamically, or integrating with virtualization. For simple "is this one element visible" cases, you may prefer the lighter-weight visibility patterns built on top of it. # useIsMobile Boolean flag that flips when the viewport crosses a mobile breakpoint. ## Usage ```tsx import { useIsMobile } from '@primereact/hooks/use-is-mobile'; ``` ```tsx const isMobile = useIsMobile(); // default breakpoint: 768px const isTablet = useIsMobile(1024); // custom breakpoint ``` Returns `true` when the viewport width is **below** the breakpoint. Implemented on top of `useMatchMedia`, so it updates live as the window resizes. ## Signature ```ts function useIsMobile(breakpoint?: number): boolean; ``` Default breakpoint is 768px. Pass any max-width you care about, the hook resolves the corresponding media query for you. # useKeyFilter Block individual keystrokes that don't match a pattern, before they reach the input. ## Usage ```tsx import { useKeyFilter } from '@primereact/hooks/use-key-filter'; ``` ```tsx const { onBeforeInput, onKeyPress, onPaste } = useKeyFilter({ pattern: 'int' }); ; ``` Spread the returned handlers onto a native input. Keystrokes and pastes that don't match the pattern are rejected before they change the value. ## Signature ```ts function useKeyFilter(options: UseKeyFilterOptions): UseKeyFilterExposes; ``` ## Options - `pattern`, a preset name or a `RegExp`. Presets: `pint`, `int`, `pnum`, `num`, `money`, `hex`, `email`, `alpha`, `alphanum`. - `validateOnly?`, when `true`, individual keys are allowed and the whole value is validated instead (use `validate` on form events). ## Returns `{ onBeforeInput, onKeyPress, onPaste, validate }`. The first three are input handlers to spread; `validate(event)` returns a boolean for the `validateOnly` flow. ## Basic ```tsx 'use client'; import { useKeyFilter } from '@primereact/hooks'; import { InputText } from '@primereact/ui/inputtext'; import { Label } from '@primereact/ui/label'; import * as React from 'react'; export default function PatternDemo() { const { onKeyPress: onIntegerKeyPress } = useKeyFilter({ pattern: 'int' }); const { onKeyPress: onNumberKeyPress } = useKeyFilter({ pattern: 'num' }); const { onKeyPress: onMoneyKeyPress } = useKeyFilter({ pattern: 'money' }); const { onKeyPress: onHexKeyPress } = useKeyFilter({ pattern: 'hex' }); const { onKeyPress: onAlphaKeyPress } = useKeyFilter({ pattern: 'alpha' }); const { onKeyPress: onAlphanumKeyPress } = useKeyFilter({ pattern: 'alphanum' }); const [integer, setInteger] = React.useState(''); const [number, setNumber] = React.useState(''); const [money, setMoney] = React.useState(''); const [hex, setHex] = React.useState(''); const [alphabetic, setAlphabetic] = React.useState(''); const [alphanumeric, setAlphanumeric] = React.useState(''); return (
) => setInteger(e.target.value)} onKeyPress={onIntegerKeyPress} className="w-full" />
) => setNumber(e.target.value)} onKeyPress={onNumberKeyPress} className="w-full" />
) => setMoney(e.target.value)} onKeyPress={onMoneyKeyPress} className="w-full" />
) => setHex(e.target.value)} onKeyPress={onHexKeyPress} className="w-full" />
) => setAlphabetic(e.target.value)} onKeyPress={onAlphaKeyPress} className="w-full" />
) => setAlphanumeric(e.target.value)} onKeyPress={onAlphanumKeyPress} className="w-full" />
); } ``` # useLocalStorage State persisted to localStorage, with SSR-safe hydration and custom serialization. ## Usage ```tsx import { useLocalStorage } from '@primereact/hooks/use-local-storage'; ``` ```tsx const [sizes, setSizes, removeSizes] = useLocalStorage({ key: 'splitter-sizes', initialValue: [50, 50] }); ``` The hook renders `initialValue` for SSR and the first client render, then reads the stored value in a layout effect, so the real value appears before paint and there's no hydration mismatch or flash. ## Options - `key`, storage key. - `initialValue`, value used when nothing is stored yet. - `serializer?` / `deserializer?`, default to `JSON.stringify` / `JSON.parse`. Override for dates, maps, or custom codecs. ## Returns `[value, setValue, remove]`. `setValue` accepts a value or an updater function (like `useState`). `remove()` clears the key from storage and resets to `initialValue`. # useMask Format an input as the user types, phone, date, currency and similar fixed patterns. ## Usage ```tsx import { useMask } from '@primereact/hooks/use-mask'; ``` ```tsx const { ref, value, rawValue, isComplete } = useMask({ mask: '(999) 999-9999' }); ; ``` Attach `ref` to any component that forwards a ref to a native input. The hook formats keystrokes against `mask` and keeps both the masked and raw values in sync. ## Signature ```ts function useMask(options: UseMaskOptions): UseMaskReturn; ``` ## Options - `mask`, pattern string: `9` numeric, `a` alphabetic, `*` alphanumeric. Literals like `(`, `)`, `-` and `/` are inserted automatically. A `?` marks everything after it as optional. - `slotChar?`, placeholder for unfilled slots (default `_`). Pass a full template like `dd/mm/yyyy` to label each slot. - `autoClear?`, clear an incomplete value on blur (default `true`). - `value?` / `defaultValue?` / `onValueChange?`, controlled / uncontrolled masked value. - `onComplete?`, fires when every required slot is filled. ## Returns `{ ref, value, rawValue, isComplete, setValue }`. `value` is the masked string, `rawValue` strips the literals and placeholders, `isComplete` is `true` once all required slots are filled. ## Basic ```tsx 'use client'; import { useMask } from '@primereact/hooks'; import { InputText } from '@primereact/ui/inputtext'; export default function BasicDemo() { const { ref } = useMask({ mask: '99-999999' }); return (
); } ``` # useMatchMedia Reactive boolean for any CSS media query. ## Usage ```tsx import { useMatchMedia } from '@primereact/hooks/use-match-media'; ``` ```tsx const prefersDark = useMatchMedia('(prefers-color-scheme: dark)'); const isNarrow = useMatchMedia('(max-width: 768px)'); ``` Subscribes to the query via `window.matchMedia` and updates on change. Toggle the `when` flag to pause listening without unmounting: ```tsx const matches = useMatchMedia('(max-width: 600px)', isActive); ``` ## Signature ```ts function useMatchMedia(query: string, when?: boolean): boolean; ``` Server renders return `false`; the hook syncs to the real value after hydration. # useMountEffect Run an effect exactly once, after the first render. ## Usage ```tsx import { useMountEffect } from '@primereact/hooks/use-mount-effect'; ``` ```tsx useMountEffect(() => { console.log('mounted'); return () => console.log('cleanup'); }); ``` Equivalent to `useEffect(fn, [])` but reads clearer at the call site. The optional return value is treated as the cleanup function, just like `useEffect`. ## Signature ```ts function useMountEffect(effect: React.EffectCallback): void; ``` # useMounted SSR-safe flag that flips to true once the component mounts on the client. ## Usage ```tsx import { useMounted } from '@primereact/hooks/use-mounted'; ``` ```tsx const mounted = useMounted(); if (!mounted) return null; return ; ``` Useful when you need to gate content that depends on browser APIs without causing a hydration mismatch. ## Options ```ts useMounted({ onMounted: () => console.log('mounted'), onUnmounted: () => console.log('unmounted') }); ``` Both callbacks are optional. ## Signature ```ts function useMounted(options?: { onMounted?: () => void; onUnmounted?: () => void }): boolean; ``` # useNumberFormatter Locale-aware number formatting and parsing built on Intl.NumberFormat, decimal or currency, with custom prefix/suffix and rounding. ## Usage ```tsx import { useNumberFormatter } from '@primereact/hooks/use-number-formatter'; ``` ```tsx const formatter = useNumberFormatter({ value: 1234.56, locale: 'en-US', mode: 'currency', currency: 'USD' }); formatter.formattedValue; // "$1,234.56" formatter.formatValue(99); // "$99.00" formatter.parseValue('$1,234.56'); // 1234.56 ``` ## Options - `value?`, optional input; if provided, `formattedValue` in the return mirrors it. - `locale?`, `localeMatcher?`, passed to `Intl.NumberFormat`. - `mode?`, `'decimal'` (default) or `'currency'`. - `currency?`, `currencyDisplay?`, ISO code and display style. - `useGrouping?`, thousands separators. Default `true`. - `minFractionDigits?`, `maxFractionDigits?`, `roundingMode?`, fraction handling. - `prefix?`, `suffix?`, additional text wrapped around the formatted value. - `min?`, lower bound; controls whether a minus sign is allowed. - `format?`, `false` returns the raw string without applying the formatter. ## Returns - `formattedValue`, formatted representation of `options.value`. - `formatValue(value)` / `parseValue(text)`, format or parse on demand. - `addWithPrecision(base, inc)`, floating-point-safe addition, useful for steppers. - Character classifiers (`isNumeralChar`, `isMinusSign`, `isDecimalSign`, `allowMinusSign`) and index helpers (`getDecimalCharIndexes`, `getCharIndexes`, `getDecimalLength`, `concatValues`) that power input-mask behaviour. - `groupChar`, `prefixChar`, `suffixChar`, resolved separator characters for the active locale. - `resolvedOptions()`, forwards `Intl.NumberFormat#resolvedOptions`. - `constructParser()`, rebuild the internal parser; the hook calls it automatically when options change. ## When to use it Anywhere you want consistent localized number rendering without maintaining your own parsers, inputs with currency/percent formatting, read-only totals, calculator-style UIs. # usePresence Coordinate enter/exit animations by delaying unmount until CSS transitions finish. ## Usage ```tsx import { usePresence } from '@primereact/hooks/use-presence'; ``` ```tsx function Dialog({ open }: { open: boolean }) { const { present, mounted, ref } = usePresence(open); if (!present) return null; return (
...
); } ``` The element stays in the tree after `open` flips to `false` until `transitionend` or `animationend` fires on it, giving your CSS a chance to run the exit animation. ## Signature ```ts function usePresence( open: boolean, fallbackMs?: number ): { present: boolean; exiting: boolean; mounted: boolean; ref: (node: HTMLElement | null) => void; }; ``` - `present`, whether the element should render. - `mounted`, set to `true` a few frames after opening; use it to toggle the visible state in CSS so the transition can play. - `exiting`, `true` while the close animation is running. - `ref`, callback ref that must be attached to the animated element. Without it the hook cannot observe the transition. # usePrevious Remember the value of a variable from the previous render. ## Usage ```tsx import { usePrevious } from '@primereact/hooks/use-previous'; ``` ```tsx const [count, setCount] = React.useState(0); const prev = usePrevious(count); React.useEffect(() => { if (prev !== undefined && prev !== count) { console.log(`changed from ${prev} to ${count}`); } }, [count, prev]); ``` Returns `null` on the first render, then the value from the prior render on every subsequent render. ## Signature ```ts function usePrevious(value: T): T | null | undefined; ``` # useProps Split incoming props into known props and extra attrs, preserving referential stability when values don't change. ## Usage ```tsx import { useProps } from '@primereact/hooks/use-props'; ``` ```tsx const defaults = { variant: 'primary', size: 'medium' }; const { props, attrs } = useProps(defaults, userProps); )} ); } ``` Setting the _target_ option binds useScrollTop to this element that has scrolling content. ### Element ```tsx 'use client'; import { ArrowUp } from '@primeicons/react'; import { useScrollTop } from '@primereact/hooks'; import { Button } from '@primereact/ui/button'; import * as React from 'react'; export default function ElementDemo() { const [target, setTarget] = React.useState(null); const { scrollToTop, visible } = useScrollTop({ target, threshold: 100 }); return (

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae et leo duis ut diam. Ultricies mi quis hendrerit dolor magna eget est lorem. Amet consectetur adipiscing elit ut. Nam libero justo laoreet sit amet. Pharetra massa massa ultricies mi quis hendrerit dolor magna. Est ultricies integer quis auctor elit sed vulputate. Consequat ac felis donec et. Tellus orci ac auctor augue mauris. Semper feugiat nibh sed pulvinar proin gravida hendrerit lectus a. Tincidunt arcu non sodales neque sodales. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Sodales ut etiam sit amet nisl purus. Cursus sit amet dictum sit amet. Tristique senectus et netus et malesuada fames ac turpis egestas. Et tortor consequat id porta nibh venenatis cras sed. Diam maecenas ultricies mi eget mauris. Eget egestas purus viverra accumsan in nisl nisi. Suscipit adipiscing bibendum est ultricies integer. Mattis aliquam faucibus purus in massa tempor nec.

{visible && ( )}
); } ``` # useSortableList Reorder items in a list, optionally transfer them between lists, with state and handlers you spread onto your own DOM. ## Usage ```tsx import { useSortableList } from '@primereact/hooks/use-sortable-list'; ``` ```tsx const { dragState, getListHandlers, getItemHandlers } = useSortableList<'source' | 'target', Item>({ onReorder: (listId, from, to) => reorder(listId, from, to), onTransfer: (sourceId, targetId, items, targetIndex) => transfer(sourceId, targetId, items, targetIndex) }); ``` Spread `getListHandlers(listId)` on the list wrapper and `getItemHandlers(listId, index, item)` on each draggable row. The hook tracks hover index internally so drop events always resolve to the right target, even when the user releases over a transitioning item. ## Options - `onReorder(listId, from, to)`, same-list move. - `onTransfer?(sourceId, targetId, items, targetIndex?)`, cross-list drop. Omit to make transfers no-ops. - `canTransfer?(sourceId, targetId)`, opt out of specific pairings. - `getSelectedItems?(listId, item)`, return an array for multi-drag (defaults to `[item]`). ## State `dragState` exposes `isDragging`, `draggedItems`, `draggedIndex`, `sourceListId`, `dragOverListId`, `dragOverIndex`, enough to style placeholders, ghosts, or drop indicators. ## Basic Reorder items inside a single list. The hook feeds you hover state so you can style the active drop target however you like. ```tsx 'use client'; import { useSortableList } from '@primereact/hooks/use-sortable-list'; import * as React from 'react'; interface Task { id: number; title: string; } const initialTasks: Task[] = [ { id: 1, title: 'Review pull requests' }, { id: 2, title: 'Write release notes' }, { id: 3, title: 'Triage bug reports' }, { id: 4, title: 'Prepare demo for Friday' }, { id: 5, title: 'Answer community questions' } ]; export default function BasicDemo() { const [tasks, setTasks] = React.useState(initialTasks); const reorder = React.useCallback((_listId: 'tasks', from: number, to: number) => { setTasks((prev) => { const next = prev.slice(); const [moved] = next.splice(from, 1); next.splice(to, 0, moved); return next; }); }, []); const { dragState, getListHandlers, getItemHandlers } = useSortableList<'tasks', Task>({ onReorder: reorder }); const listHandlers = getListHandlers('tasks'); return (
    {tasks.map((task, index) => { const itemHandlers = getItemHandlers('tasks', index, task); const isDragging = dragState.isDragging && dragState.draggedIndex === index; const isOver = dragState.dragOverIndex === index && !isDragging; return (
  • {index + 1} {task.title} drag
  • ); })}
); } ``` ## Transfer between lists Pass `onTransfer` alongside `onReorder` to move items across lists. The handler receives the source list, target list, dragged items, and the target index. ```tsx 'use client'; import { useSortableList } from '@primereact/hooks/use-sortable-list'; import * as React from 'react'; interface Item { id: number; label: string; } type ListId = 'todo' | 'done'; const initial: Record = { todo: [ { id: 1, label: 'Draft the proposal' }, { id: 2, label: 'Send invoices' }, { id: 3, label: 'Update changelog' } ], done: [ { id: 4, label: 'Ship v1.0' }, { id: 5, label: 'Fix login bug' } ] }; export default function TransferDemo() { const [lists, setLists] = React.useState(initial); const reorder = React.useCallback((listId: ListId, from: number, to: number) => { setLists((prev) => { const next = prev[listId].slice(); const [moved] = next.splice(from, 1); next.splice(to, 0, moved); return { ...prev, [listId]: next }; }); }, []); const transfer = React.useCallback((source: ListId, target: ListId, items: Item[], targetIndex?: number) => { setLists((prev) => { const ids = new Set(items.map((i) => i.id)); const src = prev[source].filter((i) => !ids.has(i.id)); const tgt = prev[target].slice(); const at = targetIndex ?? tgt.length; tgt.splice(at, 0, ...items); return { ...prev, [source]: src, [target]: tgt }; }); }, []); const { dragState, getListHandlers, getItemHandlers } = useSortableList({ onReorder: reorder, onTransfer: transfer }); const renderList = (listId: ListId, title: string) => { const items = lists[listId]; const listHandlers = getListHandlers(listId); const isTarget = dragState.dragOverListId === listId && dragState.sourceListId !== listId; return (

{title}

    {items.map((item, index) => { const itemHandlers = getItemHandlers(listId, index, item); const isDragging = dragState.isDragging && dragState.sourceListId === listId && dragState.draggedIndex === index; const isOver = dragState.dragOverListId === listId && dragState.dragOverIndex === index && !isDragging; return (
  • {item.label}
  • ); })} {items.length === 0 &&
  • Drop items here
  • }
); }; return (
{renderList('todo', 'To Do')} {renderList('done', 'Done')}
); } ``` ## Notes Drops are handled at the **list level**, not per-item. That means you can animate items freely without breaking drop targeting, and you don't need to attach `onDrop` to each row. ## Related For PrimeReact's component-level drag-and-drop primitives, composable `useDraggable`, `useDroppable`, and `useSortableContainer` with a shared coordinator, see the headless `dnd` package. Those power components like OrderList and PickList. # useTreeFilter Filter hook for hierarchical data. Lenient or strict branch matching. ## Usage ```tsx import { useTreeFilter } from '@primereact/hooks/use-tree-filter'; ``` ```tsx const { filteredNodes, value, setValue, isFiltered } = useTreeFilter({ nodes, field: 'label' }); ``` `useTreeFilter` walks nodes with `children` and returns the matching branches. Rules and matchers are the same as `useFilter`, pick the hook that matches your data shape. ## Signature ```ts function useTreeFilter(options: UseTreeFilterOptions): UseTreeFilterReturn; ``` ## filterMode - **`lenient`** (default), keep a branch if it matches, or if any descendant matches. The whole subtree is included. - **`strict`**, leaves must match on their own. Parents are kept only as a path to a matching descendant; non-matching leaves are dropped. ```tsx 'use client'; import { File } from '@primeicons/react/file'; import { Folder } from '@primeicons/react/folder'; import { Search } from '@primeicons/react/search'; import { useTreeFilter, type TreeFilterMode } from '@primereact/hooks/use-tree-filter'; import { IconField } from '@primereact/ui/iconfield'; import { InputText } from '@primereact/ui/inputtext'; import { ToggleButton } from '@primereact/ui/togglebutton'; import { ToggleButtonGroup } from '@primereact/ui/togglebuttongroup'; import * as React from 'react'; interface Node { key: string; data: { name: string; type: 'folder' | 'file' }; children?: Node[]; } const tree: Node[] = [ { key: '0', data: { name: 'Documents', type: 'folder' }, children: [ { key: '0-0', data: { name: 'Work Report.docx', type: 'file' } }, { key: '0-1', data: { name: 'Expenses.xlsx', type: 'file' } }, { key: '0-2', data: { name: 'Invoices', type: 'folder' }, children: [ { key: '0-2-0', data: { name: 'January.pdf', type: 'file' } }, { key: '0-2-1', data: { name: 'February.pdf', type: 'file' } } ] } ] }, { key: '1', data: { name: 'Media', type: 'folder' }, children: [ { key: '1-0', data: { name: 'Holiday.mp4', type: 'file' } }, { key: '1-1', data: { name: 'Wallpaper.jpg', type: 'file' } } ] }, { key: '2', data: { name: 'Archive', type: 'folder' }, children: [{ key: '2-0', data: { name: 'old-work-report.docx', type: 'file' } }] } ]; function renderNode(node: Node, level = 0): React.ReactNode { return (
{node.data.type === 'folder' ? ( ) : ( )} {node.data.name}
{node.children?.map((c) => renderNode(c, level + 1))}
); } export default function TreeDemo() { const [mode, setMode] = React.useState('lenient'); const { filteredNodes, rules, setRules } = useTreeFilter({ nodes: tree, defaultRules: [{ field: 'name', value: null, matchMode: 'contains' }], filterMode: mode }); const search = (rules[0]?.value as string) ?? ''; return (
) => setRules([{ field: 'name', value: e.target.value, matchMode: 'contains' }]) } /> setMode(e.value as TreeFilterMode)} > Lenient Strict
{filteredNodes.length === 0 ? (
No matches.
) : ( filteredNodes.map((n) => renderNode(n)) )}

Lenient: a branch is kept if any descendant matches. Strict: leaves must match; parents are kept only when a descendant matches.

); } ``` ## Rules Same shape as `useFilter`. Fields resolve against `node.data` first, then against the node itself. So a flat `{ key, label, children }` works the same as the DataTable-style `{ key, data: { name, type }, children }`. ```tsx // node.data.name useTreeFilter({ nodes, defaultRules: [{ field: 'name', value: 'doc', matchMode: 'contains' }], filterMode: 'lenient' }); // node.label (top-level) useTreeFilter({ nodes, defaultRules: [{ field: 'label', value: 'doc', matchMode: 'contains' }] }); ``` ## Shorthand For single-field filtering, skip the rules array and pass `field` directly. The hook builds a `{ field, value, matchMode }` rule for you and exposes `value` / `setValue` for the input. ```tsx const { filteredNodes, value, setValue, isFiltered } = useTreeFilter({ nodes, field: 'label', matchMode: 'contains' // optional, defaults to 'contains' }); setValue(e.target.value)} />; ``` `value` follows the standard controlled / uncontrolled convention: | Mode | Setup | When to use | | ---------------- | -------------------------------------------- | --------------------------------------------------------------------------- | | **Uncontrolled** | omit `value`, optionally pass `defaultValue` | Default. The hook keeps the state and `setValue` updates it. | | **Controlled** | pass both `value` and `onValueChange` | When the parent already owns the query (URL sync, parent re-renders, etc.). | Passing `rules` or `defaultRules` turns the shorthand off, use one or the other. ## Lazy With `lazy: true` the client-side walk is skipped and `filteredNodes` is returned untouched. `onLazyLoad` fires with the current rules and the active `filterMode`; forward both so your backend can apply the same branch logic. ```tsx useTreeFilter({ nodes, lazy: true, defaultRules: [{ field: 'label', value: null }], filterMode: 'lenient', onLazyLoad: ({ rules, filterMode }) => { // fetch filtered tree from the server } }); ``` ## Custom matchers Reuse `matchers` and the shared `registerMatcher` / `unregisterMatcher` helpers from `@primereact/hooks/use-filter`. # useUnmountEffect Run an effect exactly once, when the component unmounts. ## Usage ```tsx import { useUnmountEffect } from '@primereact/hooks/use-unmount-effect'; ``` ```tsx useUnmountEffect(() => { socket.close(); }); ``` Equivalent to `useEffect(() => cleanup, [])`, expressed as the cleanup by itself. Reads clearer when the mount path has nothing to do. ## Signature ```ts function useUnmountEffect(effect: React.EffectCallback): void; ``` # useUpdateEffect useEffect that skips the initial mount and runs only on subsequent dependency changes. ## Usage ```tsx import { useUpdateEffect } from '@primereact/hooks/use-update-effect'; ``` ```tsx useUpdateEffect(() => { onValueChange(value); }, [value]); ``` The effect does not run on the first render. Every time one of the dependencies changes afterwards, it fires like a normal `useEffect`. ## Signature ```ts function useUpdateEffect(effect: React.EffectCallback, deps?: React.DependencyList): void; ``` ## When to use it Triggering side effects that only make sense _after_ a user-driven change, firing a controlled `onChange`, syncing to a remote store, or announcing transitions. # useViewTransition Wrap state updates in the View Transitions API, with a graceful fallback on unsupported browsers. ## Usage ```tsx import { useViewTransition } from '@primereact/hooks/use-view-transition'; ``` ```tsx const startTransition = useViewTransition(); function toggleTheme() { startTransition(() => { setTheme(theme === 'light' ? 'dark' : 'light'); }); } ``` Calls `document.startViewTransition(callback)` when the API exists; otherwise the callback runs synchronously. That means your code always works, the animation is the enhancement. ## Signature ```ts function useViewTransition(): (callback: () => void) => void; ``` ## Notes View Transitions are driven by CSS (`::view-transition-*` pseudo-elements and `view-transition-name`). This hook is a small API hook, pair it with the CSS your design system defines. # useVisibilityChange Reactive boolean that reflects the document's Page Visibility state. ## Usage ```tsx import { useVisibilityChange } from '@primereact/hooks/use-visibility-change'; ``` ```tsx const isVisible = useVisibilityChange(); React.useEffect(() => { if (!isVisible) pauseTimers(); else resumeTimers(); }, [isVisible]); ``` Listens to the `visibilitychange` event and returns `true` while the tab is active, `false` when backgrounded or minimized. ## Signature ```ts function useVisibilityChange(): boolean; ``` ## When to use it Throttle or pause work while the tab isn't visible, polling requests, animations, WebSocket pings, and resume when the user comes back. # Add-Ons Premium templates, UI blocks and design tooling that build on top of PrimeReact. ## Overview Add-Ons are premium, production-ready resources built on top of PrimeReact. They help you skip the boilerplate and ship faster, from full application templates to ready-made UI blocks and design tooling. ## What's included - **Templates**, complete, themeable application starters with layouts, pages and integrations wired up. - **Blocks**, copy-and-paste UI sections for application, marketing and e-commerce pages. - **Figma UI Kit**, the design source that mirrors the component library, for designing before you build. - **Theme Designer**, a visual tool to craft and export your own design tokens and themes. Pick a category from the sidebar to explore each add-on. # Resources Support channels, legacy versions and places to discover more about PrimeReact. ## Overview Everything around the library, where to get help, find older versions, and stay up to date. ## What's here - **Support**, get help on Discord, the community forum, or through premium support. - **Legacy**, documentation for previous major versions. - **Discover**, the store and social channels. Pick a category from the sidebar to jump in. # Configuration Application wide configuration for PrimeReact. ## Provider Wrap your app with `PrimeReactProvider` in your main file (like `main.tsx` or `App.tsx`). This enables PrimeReact features everywhere in your app: ```tsx showLineNumbers {10, 12} import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import { PrimeReactProvider } from "@primereact/core"; ... createRoot(document.getElementById('root')!).render( , ) ``` ## License PrimeReact is a commercially licensed library. Provide your license key via the `license` property of the provider to activate it. You can obtain a key from your [PrimeUI](https://primeui.dev/pricing) account. ```tsx showLineNumbers {4} import { PrimeReactProvider } from '@primereact/core'; ReactDOM.createRoot(document.getElementById('root')).render( ); ``` ## Theme Style mode offers theming based on a design token based architecture. See the [styled mode](/docs/theming/styled) documentation for details such as building your own theme. ```tsx full showLineNumbers {2,5-12,16} ... import Aura from '@primeuix/themes/aura'; import { PrimeReactProvider } from '@primereact/core'; const theme = { preset: Aura, options: { prefix: 'p', darkModeSelector: 'system', cssLayer: false, cssVariables: true, scoped: false } }; ReactDOM.createRoot(document.getElementById('root')).render( ); ``` ## Unstyled Unstyled mode instructs the components not to add any built-in style classes so that they can be styled using custom css or libraries like Tailwind and Bootstrap. Visit Unstyled mode documentation for more information. ```tsx showLineNumbers {6} import { PrimeReactProvider } from '@primereact/core'; ... ReactDOM.createRoot(document.getElementById('root')).render( ); ``` The same prop works on a single component, which takes precedence over the provider. This suits an application that is themed overall but has one screen styled differently. ```tsx … ``` There is also a third option that does not involve the flag at all. Importing from `primereact` gives the unstyled primitives directly, so the theming layer never enters the bundle: ```tsx import { Select } from '@primereact/ui/select'; // themed, opt out with `unstyled` import { Select } from 'primereact/select'; // unstyled by construction ``` ## Preflight Preflight resets the browser defaults on the native elements PrimeReact renders, such as `button`, `input`, `ul`, `table`, `img` and the heading tags. It removes the margins, borders, font inheritance and list styling browsers apply by default, so a component's own styles are the only ones in effect. It is on by default. The reset only applies to elements PrimeReact renders itself, scoped through the `data-scope` attribute each component sets. A ` ); } ``` ## Import ```js import { $t, $l, defineLocale, updateLocale, useLocale, Locale, LocaleService, en } from '@primereact/core/locale'; ``` ## Setting the Language `Locale.use` selects the active language. Every component reading a message re-renders when it changes, so this works both at startup and later in response to a language switcher. ```js import { Locale } from '@primereact/core/locale'; Locale.use('de'); ``` Only `en` is built in, so any other language has to be registered first. See [Adding a Language](#adding-a-language) below. `PrimeReactProvider` also accepts a `locale` prop, which sets the initial language for the components beneath it. ```jsx import { PrimeReactProvider } from '@primereact/core'; ; ``` ## Adding a Language `defineLocale` registers a new language. ```js import { defineLocale, Locale } from '@primereact/core/locale'; defineLocale('es', { clear: 'Limpiar', apply: 'Aplicar', accept: 'Sí', reject: 'No', dayNames: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], dayNamesShort: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'], dayNamesMin: ['D', 'L', 'M', 'X', 'J', 'V', 'S'], monthNames: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], monthNamesShort: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'] }); Locale.use('es'); ``` A registered language must be complete. Keys that are left out resolve to `undefined` rather than falling back to English, which renders as an empty label. The practical approach is to spread the built-in English messages and override from there: ```js import { defineLocale, en } from '@primereact/core/locale'; defineLocale('es', { ...en, clear: 'Limpiar', apply: 'Aplicar' }); ``` The `en` object holds the built-in English messages and is exported for exactly this purpose. Ready-made translations from [PrimeLocale](#ready-made-translations) already cover every key, so spreading is only needed for hand-written translations. The provider accepts a `locales` map, which registers languages and selects one in a single step. Anything listed here is registered before `locale` is applied. ```jsx import { PrimeReactProvider } from '@primereact/core'; ; ``` ## Updating an Existing Language `updateLocale` merges new values into a language that is already registered, leaving the remaining keys untouched. It is useful for overriding a handful of labels without redefining a full translation. ```js import { updateLocale } from '@primereact/core/locale'; updateLocale('en', { clear: 'Reset', apply: 'Confirm' }); ``` If the language does not exist yet, `updateLocale` registers it, behaving like `defineLocale`. ## Reading Messages ### In Components `useLocale` is the React hook for reading the active locale. It re-renders the calling component when the language changes. ```jsx import { Button } from '@primereact/ui/button'; import { useLocale } from '@primereact/core/locale'; function ClearFilterButton({ onClear }) { const { t } = useLocale(); return ( ); } ``` | Field | Description | | ---------- | --------------------------------------------- | | `lang` | Active language code, such as `en` or `de`. | | `messages` | Full message object of the active language. | | `t` | Translates a key against the active language. | Re-rendering on a language change relies on the context published by `PrimeReactProvider`, which applications normally have at their root. A component rendered outside any provider still reads the correct messages on first render, but will not update when the language changes later. ### Outside Components `$t` performs the same lookup without a React context, which suits utilities, event handlers and module-level code. ```js import { $t } from '@primereact/core/locale'; $t('clear'); // 'Clear' ``` Nested keys use dot notation. ```js $t('aria.selectRow'); // 'Row Selected' ``` Messages containing placeholders accept replacement values. Positional placeholders such as `{0}` are filled from the argument order, while named ones are filled from an object. ```js $t('searchMessage', 5); // '5 results are available' $t('selectionMessage', 3); // '3 items selected' $t('aria.stars', { star: 4 }); // '4 stars' ``` ## Reacting to Language Changes Language changes emit a `locale:change` event. Components handle this automatically through `useLocale`; the event is only needed for code living outside React, such as syncing a third-party library. ```js import { LocaleService } from '@primereact/core/locale'; const onChange = ({ lang }) => console.log('Language is now', lang); LocaleService.on('locale:change', onChange); // remove the listener when it is no longer needed LocaleService.off('locale:change', onChange); ``` ## Inspecting the Registry `$l` returns a facade over the registry, which helps when building a language switcher. ```js import { $l } from '@primereact/core/locale'; $l().langs; // ['en', 'es'] $l().get('es'); // message object of the Spanish locale ``` ## Message Keys These components read their labels from the active language: | Component | Keys | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | DatePicker | `dayNames`, `dayNamesShort`, `dayNamesMin`, `monthNames`, `monthNamesShort`, `today`, `weekHeader`, `clear`, `am`, `pm`, `firstDayOfWeek`, `dateFormat` | | FileUpload | `fileSizeTypes` | | Paginator | `aria.firstPageLabel`, `aria.prevPageLabel`, `aria.nextPageLabel`, `aria.lastPageLabel`, `aria.pageLabel` | | Sidebar | `aria.toggleSidebar` | The registry holds a larger set covering filter operators, password strength labels, empty-state messages and the remaining `aria` labels. Those are reserved for components that do not consume them yet, so translating them has no visible effect today. Not every entry is a string. `firstDayOfWeek` is a number, `showMonthAfterYear` is a boolean, and `dayNames`, `monthNames` and `fileSizeTypes` are arrays. `searchMessage` and similar entries carry a `{0}` placeholder. The full set with default English values is exported as `en`, which can also be inspected at runtime: ```js import { en } from '@primereact/core/locale'; Object.keys(en); // every top-level key ``` ## Ready-Made Translations Writing a translation from scratch is rarely necessary. The community maintained [PrimeLocale](https://github.com/primefaces/primelocale) repository publishes complete translations for 70+ languages, shared across all Prime libraries. Each file covers every built-in message key, so nothing is left resolving to `undefined`. ### Copying a File Grab the JSON file for the desired language from the repository, for example `es.json`, and place it in the project. Each file wraps its messages in a single key named after the language: ```json { "es": { "clear": "Limpiar", "apply": "Aplicar", "aria": { "selectRow": "Seleccionar fila" } } } ``` That wrapper has to be unwrapped before registering, otherwise every lookup resolves to `undefined`: ```js import { defineLocale, Locale } from '@primereact/core/locale'; import es from './locales/es.json'; defineLocale('es', es.es); Locale.use('es'); ``` The same unwrapping applies when the language is registered through the provider: ```jsx import { PrimeReactProvider } from '@primereact/core'; import es from './locales/es.json'; ; ``` ### Installing the Package The translations are also published to npm, which avoids maintaining copies by hand. ```bash npm install primelocale ``` ```js import { defineLocale, Locale } from '@primereact/core/locale'; import { es } from 'primelocale/js/es.js'; defineLocale('es', es); Locale.use('es'); ``` The package entry points export the messages directly, so no unwrapping is needed there. Regional variants are named with an underscore in the package and a hyphen in the JSON files. Brazilian Portuguese is `primelocale/js/pt_BR.js` when imported and `pt-BR.json` when copied. ### Adjusting a Translation A ready-made translation can be tuned without editing the file, by layering overrides on top of it: ```js defineLocale('es', es.es); updateLocale('es', { clear: 'Borrar' }); ``` Contributions of new languages and corrections are welcome in the PrimeLocale repository, and benefit every Prime library at once. ## Coming from v10 The locale helpers from `primereact/api` were replaced. The [migration guide](/docs/styled/guides/migration/updating-to-v11) maps each one to its replacement. # Pass Through The Pass Through attributes is an API to access the internal DOM Structure of the components. ## Introduction In traditional 3rd party UI libraries, users are limited to the API provided by component author. This API commonly consists of props, events and slots. Whenever a requirement emerges for a new customization option in the API, the component author needs to develop and publish it with a new release. Vision of PrimeTek is _Your components, not ours_. The pass through feature is a key element to implement this vision by exposing the component internals in order to apply arbitrary attributes and listeners to the DOM elements. The primary advantage of this approach is that it frees you from being restricted by the main component API. We recommend considering the pass-through feature whenever you need to tailor a component that lacks a built-in feature for your specific requirement. ## Basic Each component has a special `pt` property to define an object with keys corresponding to the available DOM elements. Each value can either be a string, an object or a function that returns a string or an object to define the arbitrary properties to apply to the element such as styling, aria, data-\* or custom attributes. If the value is a string or a function that returns a string, it is considered as a className definition and added to the className attribute of the element. Every component documentation has a dedicated section to document the available section names exposed via PT. Most common usage of `pt` is styling and customization. Example below styles an unstyled Panel component with Tailwind CSS library. ```tsx import { Panel } from 'primereact/panel'; export default function BasicDemo() { return (
Header

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

); } ``` ## Global Defines the shared pass through properties per component type. For example, with the configuration below all panel headers have the `bg-primary` style class and the all autocomplete components have a fixed width. These settings can be overriden by a particular component as components `pt` property has higher precedence over global `pt`. ```tsx pt: { panel: { header: { className: 'bg-primary text-primary-contrast' } }, autocomplete: { input: { root: 'w-64' // OR { class: 'w-64' } } } } ``` ## Custom CSS The `global` property has a `css` option to define custom css that belongs to a global `pt` configuration. Common use case of this feature is defining global styles and animations related to the pass through configuration. ```tsx pt: { global: { css: ` .my-button { border-width: 2px; } ` }, button: { root: 'my-button' } } ``` # RTL Support Right-to-left direction support of PrimeReact. ## Configuration The PrimeReact components natively support Right-to-Left (RTL) text direction through a modern CSS implementation utilizing FlexBox and classes like `*-inline-start` and `*-block-end`. Consequently, no JavaScript configuration is necessary; setting the document's text direction to RTL is sufficient to enable this feature. The RTL setting can either be set using the `dir` attribute or with the `direction` style property. ### With Markup ```html ``` ### With CSS ```css html { direction: rtl; } ``` ## Limitations RTL is widely supported by the UI suite except the Galleria and Carousel components. These components will be enhanced with a modern implementation in upcoming versions with built-in support for RTL. # Isolated Mode Give an app its own theme, so several apps can share a page or render in shadow roots. ## Overview By default every PrimeReact app on a page shares one theme. Its styles go to the document head and its design tokens to `:root`, so a second app on that page restyles the first one, and an app inside a shadow root does not see the styles at all. Isolated mode gives an app its own theme. The app's root element, its components and its overlays carry a `data-p-theme` attribute with the app's key, every rule is scoped to that attribute, and its styles go into each root the app renders in, the document or a shadow root. Overlays appended to the body and the dark mode follow the same scope. It is meant for micro frontends, embeddable widgets and web components, and an app that is alone on its page does not need it. The API is experimental and may change based on feedback. ## Setup Pass the result of `withIsolation` to the `isolation` prop of `PrimeReactProvider` and keep the rest of the configuration. The provider reads it once on mount. Give the element the app renders into as `root`, so the app's own markup carries the key as well. ```tsx import Aura from '@primeuix/themes/aura'; import { PrimeReactProvider } from '@primereact/core'; import { withIsolation } from '@primereact/core/isolation'; import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const container = document.getElementById('root'); const isolation = withIsolation({ key: 'shop', root: container }); ReactDOM.createRoot(container).render( ); ``` ## Root The theme variables, such as `--p-content-border-color`, are defined on elements that carry the key. PrimeReact puts the key on every element it renders, but the provider renders no element of its own, so it cannot reach the app's own markup without `root`. With `root`, CSS written for the app reads the variables in the same way as the components do: ```css .order-list { border: 1px solid var(--p-content-border-color); background: var(--p-content-background); } ``` The root carries the key while the provider is mounted and loses it on unmount. Without `root`, only the elements PrimeReact renders read the variables. ## Key The key names the app's scope. It may contain letters, digits, `_` and `-`, up to 128 characters. Without a key the browser picks a random one. When another app on the page already holds the key, the new app gets a `-2`, `-3` suffix. A key is only needed when custom CSS refers to it, since the server derives it as well. ## CSS Layer Scoping gives the theme rules a higher specificity than a plain selector. A plain rule such as `.p-button { ... }` loses to the scoped rule, whether it comes from the app or from the host page. `cssLayer` moves the theme into a cascade layer, and CSS without a layer wins again as in the default mode. Which setting fits depends on who writes the other CSS on the page. The app owns the page: enable `cssLayer`, so overrides written for the app work as in the default mode. The page belongs to someone else, as with a browser extension or an embedded widget: leave `cssLayer` off, so the page's own `.p-button` rules do not override the theme's rules. For a full separation from the host CSS, render the app in a shadow root. Isolated mode places its styles there. ```tsx ``` ## Isolation API `useIsolation` returns the isolation of the current app, or `undefined` in the default mode. Use `retain` to bring the app's styles into a shadow root it does not render in yet, `hold` to put an element appended outside the app inside its scope, and `set` with `scopeCSS` to add custom CSS to the app's scope. ```tsx import { useIsolation } from '@primereact/core'; import * as React from 'react'; export function Widget({ shadowRoot }) { const isolation = useIsolation(); React.useEffect(() => { if (!isolation) return; const release = isolation.retain(shadowRoot); isolation.set('my-styles', isolation.scopeCSS('.my-card { padding: 1rem; }')); return () => { release(); isolation.delete('my-styles'); }; }, [isolation, shadowRoot]); return null; } ``` ## Shadow Root An app that keeps the default mode but renders components inside shadow roots uses `withShadowRoot` instead, which copies the page styles into the shadow roots that PrimeReact components render in. Isolated mode already does this, so the two options are not combined. Set the option on the outermost provider. A nested `ThemeProvider` does not inherit it. ```tsx import Aura from '@primeuix/themes/aura'; import { PrimeReactProvider } from '@primereact/core'; import { withShadowRoot } from '@primereact/core/shadowroot'; ; ``` ## Limits Isolated mode keeps the app's theme and styles apart from other apps. It does not stop CSS on the page from matching the app's elements; only a shadow root does that. One isolated app inside another is not supported. # Styled Mode Choose from a variety of pre-styled themes or develop your own. ## Architecture PrimeReact is a design agnostic library so unlike some other UI libraries it does not enforce a certain styling such as material design. Styling is decoupled from the components using the themes instead. A theme consists of two parts; _base_ and _preset_. The base is the style rules with CSS variables as placeholders whereas the preset is a set of design tokens to feed a base by mapping the tokens to CSS variables. A base may be configured with different presets, currently Aura, Material, Lara and Nora are the available built-in options. ![Architecture](https://primefaces.org/cdn/primevue/images/primevue-v4-styled-architecture.png) The core of the styled mode architecture is based on a concept named _design token_, a preset defines the token configuration in 3 tiers; _primitive_, _semantic_ and _component_. Learn more about design tokens at the [Design Tokens Format Module](https://www.designtokens.org/tr/drafts/format/) specification. ### Primitive Tokens Primitive tokens have no context, a color palette is a good example for a primitive token such as `blue-50` to `blue-900`. A token named `blue-500` may be used as the primary color, the background of a message however on its own, the name of the token does not indicate context. Usually they are utilized by the semantic tokens. ### Semantic Tokens Semantic tokens define content and their names indicate where they are utilized, a well known example of a semantic token is the `primary.color`. Semantic tokens map to primitive tokens or other semantic tokens. The `colorScheme` token group is a special variable to define tokens based on the color scheme active in the application, this allows defining different tokens based on the color scheme like dark mode. ### Component Tokens Component tokens are isolated tokens per component such as `inputtext.background` or `button.color` that map to the semantic tokens. As an example, `button.background` component token maps to the `primary.color` semantic token which maps to the `green.500` primitive token. ### Best Practices Use primitive tokens when defining the core color palette and semantic tokens to specify the common design elements such as focus ring, primary colors and surfaces. Components tokens should only be used when customizing a specific component. By defining your own design tokens as a custom preset, you'll be able to define your own style without touching CSS. Overriding the PrimeReact components using style classes is not a best practice and should be the last resort, design tokens are the suggested approach. ## Configuration API ### Theme The `theme` property is used to customize the initial theme. ```tsx import Aura from '@primeuix/themes/aura'; import { PrimeReactProvider } from '@primereact/core'; import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const theme = { preset: Aura, // Default options options: { prefix: 'p', darkModeSelector: 'system', cssLayer: false, cssVariables: true, scoped: false } }; ReactDOM.createRoot(document.getElementById('root')).render( ); ``` ### Options The `options` property defines the how the CSS would be generated from the design tokens of the preset. #### prefix The prefix of the CSS variables, defaults to `p`. For instance, the `primary.color` design token would be `var(--p-primary-color)`. ```tsx options: { prefix: 'my'; } ``` #### darkModeSelector The CSS rule to encapsulate the CSS variables of the dark mode, the default is the `system` to generate `@media (prefers-color-scheme: dark)`. If you need to make the dark mode toggleable based on the user selection define a class selector such as `.app-dark` and toggle this class at the document root. See the dark mode toggle section for an example. ```tsx options: { darkModeSelector: '.my-app-dark'; } ``` #### cssLayer Defines whether the styles should be defined inside a [CSS layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) by default or not. A CSS layer would be handy to declare a custom cascade layer for easier customization if necessary. The default is `false`. ```tsx options: { cssLayer: { name: 'primereact', order: 'app-styles, primereact, another-css-library' } } ``` #### cssVariables Controls whether component design tokens are generated as CSS variables or inlined as static values. Primitive and semantic tokens are always generated as CSS variables. #### scoped Scopes the generated CSS variables to the components only instead of the document root and host elements. Defaults to `false`. #### classPrefix Sets the prefix of the component class names, `p` by default. With `my`, the `p-button` class becomes `my-button` in both the generated CSS and the rendered markup. The icon classes `p-icon` and `p-icon-*` keep the `p-` name, and classes passed from the app, such as `className`, are not renamed. ```tsx options: { classPrefix: 'my'; } ``` ### Presets Aura, Material, Lara and Nora are the available built-in options, created to demonstrate the power of the design-agnostic theming. Aura is PrimeTek's own vision, Material follows Google Material Design v2, Lara is based on Bootstrap and Nora is inspired by enterprise applications. Visit the [source code](https://github.com/primefaces/primeuix/tree/main/packages/themes/src/presets) to learn more about the structure of presets. You may use them out of the box with modifications or utilize them as reference in case you need to build your own presets from scratch. ### Reserved Keys Following keys are reserved in the preset scheme and cannot be used as a token name; `primitive`, `semantic`, `components`, `directives`, `colorscheme`, `light`, `dark`, `common`, `root`, `states`, and `extend`. ### Colors Color palette of a preset is defined by the `primitive` design token group. You can access colors using CSS variables or the `$dt` utility. ```tsx // With CSS var(--p-blue-500) // With JS $dt('blue.500').value ``` ```tsx export default function ColorsListDemo() { const colors = [ 'emerald', 'green', 'lime', 'red', 'orange', 'amber', 'yellow', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose', 'slate', 'gray', 'zinc', 'neutral', 'stone' ]; const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]; return (
    {colors.map((color, i) => (
  • {color}
    {shades.map((shade, j) => (
    {shade}
    ))}
  • ))}
); } ``` ## Dark Mode PrimeReact uses the `system` as the default `darkModeSelector` in theme configuration. If you have a dark mode switch in your application, set the `darkModeSelector` to the selector you utilize such as `.my-app-dark` so that PrimeReact can fit in seamlessly with your color scheme. ```tsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import { PrimeReactProvider } from '@primereact/core'; import Aura from '@primeuix/themes/aura'; const primereact = { theme: { preset: Aura, options: { darkModeSelector: '.my-app-dark' } } }; createRoot(document.getElementById('root')!).render( ); ``` Following is a very basic example implementation of a dark mode switch, you may extend it further by involving `prefers-color-scheme` to retrieve it from the system initially and use `localStorage` to make it stateful. See this [article](https://dev.to/abbeyperini/dark-mode-toggle-and-prefers-color-scheme-4f3m) for more information. ```tsx ); } ``` ## Example Here is a sample that styles a button component with Tailwind CSS using [passthrough](/docs/passthrough) attributes. Before beginning, head over to the the pass through section at [button](/docs/button) documentation to learn more about the components internals section. We'll be using the `root` element to add a custom style. ```tsx import { Button } from '@primereact/ui/button'; import { Search } from '@primeicons/react/search'; export default function ButtonDemo() { return (
); } ``` ## Global A global configuration can be created at application level to avoid repetition via the global `pt` option so that the styles can be shared from a single location. A particular component can still override a global configuration with its own `pt` property. ```tsx showLineNumbers import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import { PrimeReactProvider } from '@primereact/core'; const globalPT = { button: { root: "bg-teal-500 hover:bg-teal-700 active:bg-teal-900 cursor-pointer py-2 px-4 rounded-full border-0 flex gap-2 text-white text-lg font-bold" } panel: { header: 'bg-primary text-primary-contrast border-primary', content: 'border-primary text-lg text-primary-700', title: 'bg-primary text-primary-contrast text-xl' } }; createRoot(document.getElementById('root')!).render( ); ``` # Accordion Accordion groups content into collapsible panels. ```tsx import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; export default function Preview() { return ( About the role and team

Join our product team as a Senior Frontend Engineer to shape the next generation of our design system. You will work closely with designers and product managers to deliver polished, accessible interfaces.

Day-to-day responsibilities

Build reusable components, drive technical decisions across the frontend stack, mentor junior engineers, and contribute to architecture reviews and engineering best practices.

What we are looking for

5+ years of experience with React and TypeScript, strong understanding of modern CSS, and a passion for building accessible, performant user interfaces at scale.

Compensation and benefits

Competitive salary, equity, fully remote setup, generous paid time off, learning stipend, and a yearly company offsite to meet the team in person.

); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/accordion.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; ``` ```tsx Title Content ``` ## Examples ### Basic ```tsx import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; export default function BasicDemo() { return ( What is this service about?

This service helps you manage your projects more efficiently by offering real-time collaboration, task tracking, and powerful analytics.

Is my data secure?

Yes. We use end-to-end encryption and follow industry best practices to ensure your data is protected. Your information is stored on secure servers and regularly backed up.

Can I upgrade or downgrade my plan later?

Absolutely. You can change your subscription plan at any time from your account settings. Changes take effect immediately, and any billing adjustments are handled automatically.

); } ``` ### Multiple Each `Accordion.Panel` should define a unique `value`. By default, a single panel is open at a time. Enable `multiple` on `Accordion.Root` to allow multiple panels to stay open. ```tsx import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; export default function MultipleDemo() { return ( What is this service about?

This service helps you manage your projects more efficiently by offering real-time collaboration, task tracking, and powerful analytics. Whether you're working solo or in a team, it's built to scale with your needs.

Is my data secure?

Yes. We use end-to-end encryption and follow industry best practices to ensure your data is protected. Your information is stored on secure servers and regularly backed up.

Can I upgrade or downgrade my plan later?

Absolutely. You can change your subscription plan at any time from your account settings. Changes take effect immediately, and any billing adjustments are handled automatically.

); } ``` ### Controlled Control the active panel state with `value` and `onValueChange`. ```tsx 'use client'; import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; import { Button } from '@/components/ui/button'; import type { AccordionRootValueChangeEvent } from 'primereact/accordion'; import { useState } from 'react'; export default function ControlledDemo() { const [activePanel, setActivePanel] = useState('1'); return (
setActivePanel(e.value as string | null)} > What is this service about?

This service helps you manage your projects more efficiently by offering real-time collaboration, task tracking, and powerful analytics.

Is my data secure?

Yes. We use end-to-end encryption and follow industry best practices to ensure your data is protected. Your information is stored on secure servers and regularly backed up.

Can I upgrade or downgrade my plan later?

Absolutely. You can change your subscription plan at any time from your account settings. Changes take effect immediately, and any billing adjustments are handled automatically.

); } ``` ### Disabled Set `disabled` on an `Accordion.Panel` to disable only that panel, or set it on `Accordion.Root` to disable all panels. ```tsx import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; export default function DisabledDemo() { return (
How do I reset my password?

You can reset your password by clicking the "Forgot password?" link on the login page. We'll send a password reset link to your registered email address.

Do you offer team accounts?

Yes. Our Team and Business plans are designed for collaboration. You can invite team members, assign roles, and manage permissions easily from your dashboard.

What happens if I exceed my usage limit?

If you go over your plan limits (e.g., storage or API requests), you'll receive a notification. You can either upgrade your plan or wait until the next billing cycle resets.

Is there a mobile app available?

Yes, we offer both iOS and Android apps so you can manage your account and stay connected on the go.

); } ``` ### With RadioButton `RadioButton` component can be used to group multiple `Accordion.Panel` components. ```tsx 'use client'; import { Accordion, AccordionContent, AccordionPanel, AccordionTrigger } from '@/components/ui/accordion'; import { Button } from '@/components/ui/button'; import { RadioButton, RadioButtonGroup } from '@/components/ui/radiobutton'; import type { AccordionRootValueChangeEvent } from 'primereact/accordion'; import type { RadioButtonGroupValueChangeEvent } from 'primereact/radiobuttongroup'; import * as React from 'react'; const items = [ { label: 'Starter Plan', description: 'Perfect for individuals getting started. Includes access to core components and community support.', value: '1', price: '$99' }, { label: 'Growth Plan', description: 'Ideal for freelancers and small teams. Unlocks advanced UI components and priority email support.', value: '2', price: '$249' }, { label: 'Scale Plan', description: 'Best for growing businesses. Includes all features, early access to new releases, and Slack/Jira support.', value: '3', price: '$499' } ]; export default function WithRadioButtonDemo() { const [selected, setSelected] = React.useState('1'); return (
setSelected(e.value as string)}> setSelected(e.value as string)} className="border border-surface-200 dark:border-surface-700 rounded-md divide-y divide-surface-200 dark:divide-surface-700 overflow-hidden" > {items.map((item) => ( setSelected(item.value)} className="cursor-pointer flex items-center justify-between w-full px-4 py-3 data-closed:text-surface-500 hover:text-surface-900 dark:hover:text-surface-0 data-open:text-surface-900 dark:data-open:text-surface-0 **:data-[scope=accordionindicator]:hidden" > {item.label} {item.price}

{item.description}

))}
); } ``` ## Accessibility ### Screen Reader Accordion headers are buttons. They use `aria-controls` to reference the content region and `aria-expanded` to reflect visibility state. The announced label can be customized with `aria-label` or `aria-labelledby` via pt. The content uses `role="region"` and an `id` that matches the header button's `aria-controls`. ### Header Keyboard Support | Key | Function | | ------------- | ---------------------------------------------------------------------------------------------------- | | `tab` | Moves focus to the next focusable element in the page tab sequence. | | `shift + tab` | Moves focus to the previous focusable element in the page tab sequence. | | `enter` | Toggles the visibility of the content. | | `space` | Toggles the visibility of the content. | | `down arrow` | Moves focus to the next header. If focus is on the last header, moves focus to the first header. | | `up arrow` | Moves focus to the previous header. If focus is on the first header, moves focus to the last header. | | `home` | Moves focus to the first header. | | `end` | Moves focus to the last header. | # AutoComplete AutoComplete is an input component that provides real-time suggestions when being typed. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import { Label } from '@/components/ui/label'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function Preview() { const [filtered, setFiltered] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query.toLowerCase(); setFiltered(query ? options.filter((item) => item.toLowerCase().includes(query)) : [...options]); }; return (
{filtered.map((item, index) => ( {item} ))} No results found Type react to see suggestions
); } const options: string[] = [ 'responsive design fundamentals', 'redux toolkit crash course', 'real-time chat app tutorial', 'reading json files in node', 'react hooks tutorial', 'react server components explained', 'react vs vue 2024', 'react native crash course', 'react 19 new features', 'react context api best practices', 'react testing library guide', 'react router v7 tutorial', 'react performance optimization', 'react suspense and lazy loading', 'react state management comparison', 'react form validation', 'react design patterns', 'react authentication tutorial', 'react with typescript beginner', 'react animation libraries', 'react deploy to production' ]; ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/autocomplete.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; ``` ```tsx {items.map((item, index) => ( {item.label} ))} ``` ## Examples ### Basic An input that filters and suggests options as the user types. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; interface Command { label: string; shortcut: string; } const commands: Command[] = [ { label: 'New File', shortcut: '⌘N' }, { label: 'Open File', shortcut: '⌘O' }, { label: 'Save', shortcut: '⌘S' }, { label: 'Save As', shortcut: '⇧⌘S' }, { label: 'Find', shortcut: '⌘F' }, { label: 'Replace', shortcut: '⌘H' }, { label: 'Go to Line', shortcut: '⌘G' }, { label: 'Toggle Sidebar', shortcut: '⌘B' }, { label: 'Split Editor', shortcut: '⌘\\' }, { label: 'Close Tab', shortcut: '⌘W' } ]; export default function BasicDemo() { const [filteredCommands, setFilteredCommands] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query.toLowerCase(); setFilteredCommands(query ? commands.filter((cmd) => cmd.label.toLowerCase().includes(query)) : [...commands]); }; return (
{filteredCommands.map((cmd, index) => (
{cmd.label} {cmd.shortcut}
))} No commands found
); } ``` ### Trigger Use `AutoComplete.Trigger` inside the root to display a trigger toggle button that opens the suggestion list. ```tsx 'use client'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner, AutoCompleteTrigger } from '@/components/ui/autocomplete'; import { InputGroup, InputGroupAddon } from '@/components/ui/inputgroup'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function TriggerDemo() { const [items, setItems] = React.useState([...Array(10).keys()]); const search = (event: AutoCompleteCompleteEvent) => { setItems(event.query ? [...Array(10).keys()].map((item) => event.query + '-' + item) : [...Array(10).keys()]); }; return (
); } ``` ### Chips Use `AutoComplete.Input` with `AutoComplete.Value` inside `AutoComplete.Trigger` to compose a chip-based tag input. Selected items are displayed as removable chips inside the value area while the input remains editable for searching. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import { Chip, ChipLabel, ChipRemove } from '@/components/ui/chip'; import { Times } from '@primeicons/react'; import { AutoComplete as PRAutoComplete, AutoCompleteValue, type AutoCompleteCompleteEvent, type AutoCompleteInputValueChangeEvent, type AutoCompleteRootInstance, type AutoCompleteValueChangeEvent } from '@primereact/ui/autocomplete'; import type { ChipRootRemoveEvent } from 'primereact/chip'; import * as React from 'react'; const allTags = [ 'JavaScript', 'TypeScript', 'React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt', 'Node.js', 'Deno', 'Python', 'Go', 'Rust', 'Java', 'GraphQL', 'REST', 'Docker', 'Kubernetes' ]; export default function ChipDemo() { const [items, setItems] = React.useState([]); const [selectedTags, setSelectedTags] = React.useState(['React', 'TypeScript']); const [inputValue, setInputValue] = React.useState(''); const justSelectedRef = React.useRef(false); const autoCompleteRef = React.useRef(null); React.useEffect(() => { setItems(allTags.filter((tag) => !selectedTags.includes(tag))); }, [selectedTags]); const search = (event: AutoCompleteCompleteEvent) => { const query = (event.query || '').toLowerCase(); setItems(allTags.filter((tag) => tag.toLowerCase().includes(query) && !selectedTags.includes(tag))); }; const onValueChange = (event: AutoCompleteValueChangeEvent) => { const value = event.value as string; if (value) { setSelectedTags((prev) => (prev.includes(value) ? prev : [...prev, value])); justSelectedRef.current = true; } }; const onInputValueChange = (event: AutoCompleteInputValueChangeEvent) => { if (justSelectedRef.current) { justSelectedRef.current = false; setInputValue(''); return; } setInputValue(event.query); }; const removeTag = (e: ChipRootRemoveEvent, tag: string) => { e.originalEvent.stopPropagation(); const currentIndex = selectedTags.indexOf(tag); const next = selectedTags.filter((t) => t !== tag); setSelectedTags(next); requestAnimationFrame(() => { if (currentIndex > 0) { const rootElement = autoCompleteRef.current?.elementRef.current; const removeButtons = rootElement?.querySelectorAll('[data-scope="chip"][data-part="remove"]'); removeButtons?.[currentIndex - 1]?.focus(); } else { autoCompleteRef.current?.focus(); } }); }; const onInputKeyDown = (e: React.KeyboardEvent) => { if (e.code === 'Backspace' && !inputValue && selectedTags.length > 0) { setSelectedTags((prev) => prev.slice(0, -1)); } }; return (
{selectedTags?.map((tag) => ( removeTag(e, tag)}> {tag} ))}
No commands found
); } ``` ### Clear Use `AutoComplete.Clear` inside the root to display a clear button that resets the input value. ```tsx 'use client'; import { AutoComplete, AutoCompleteClear, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import { IconField, IconFieldInset } from '@/components/ui/iconfield'; import { Tag } from '@/components/ui/tag'; import { Times } from '@primeicons/react'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; const productCategories = [ { label: 'Electronics', value: 'electronics', count: 1247 }, { label: 'Clothing', value: 'clothing', count: 856 }, { label: 'Garden', value: 'home', count: 634 }, { label: 'Sports', value: 'sports', count: 421 }, { label: 'Books', value: 'books', count: 2103 }, { label: 'Toys', value: 'toys', count: 312 } ]; export default function ClearIconDemo() { const [filteredCategories, setFilteredCategories] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query.toLowerCase(); setFilteredCategories(query ? productCategories.filter((cat) => cat.label.toLowerCase().includes(query)) : [...productCategories]); }; return (
{filteredCategories.map((cat, index) => (
{cat.label} {cat.count}
))} No categories found
); } ``` ### Custom Option Customize option content using a render function inside `AutoComplete.Option` that receives the option instance including the `selected` state. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent, AutoCompleteOptionInstance } from 'primereact/autocomplete'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import * as React from 'react'; interface TeamMember { id: number; name: string; role: string; avatar: string; status: 'online' | 'away' | 'offline'; statusClass: string; } const teamMembers: TeamMember[] = [ { id: 1, name: 'Sarah Chen', role: 'Engineering Lead', avatar: 'SC', status: 'online', statusClass: 'bg-green-400' }, { id: 2, name: 'Alex Rivera', role: 'Senior Developer', avatar: 'AR', status: 'online', statusClass: 'bg-green-400' }, { id: 3, name: 'Jordan Kim', role: 'UX Designer', avatar: 'JK', status: 'away', statusClass: 'bg-amber-400' }, { id: 4, name: 'Taylor Morgan', role: 'Product Manager', avatar: 'TM', status: 'offline', statusClass: 'bg-zinc-400' }, { id: 5, name: 'Morgan Lee', role: 'DevOps Engineer', avatar: 'ML', status: 'online', statusClass: 'bg-green-400' }, { id: 6, name: 'Casey Jones', role: 'QA Engineer', avatar: 'CJ', status: 'away', statusClass: 'bg-amber-400' } ]; export default function OptionDemo() { const [filteredMembers, setFilteredMembers] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query.toLowerCase(); setFilteredMembers( query ? teamMembers.filter((m) => m.name.toLowerCase().includes(query) || m.role.toLowerCase().includes(query)) : [...teamMembers] ); }; return (
{filteredMembers.map((member, index) => ( {(instance: AutoCompleteOptionInstance) => { const { selected } = instance; return (
{member.avatar}
{member.name} {member.role}
); }}
))} No members found
); } ``` ### Force Selection Use the `forceSelection` property to validate manual input against the suggestions list. If no match is found, the input value is cleared to ensure the value is always from the suggestion list. ```tsx 'use client'; import { CountryService } from '@/shared/services/country.service'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; interface Country { name: string; code: string; } export default function ForceSelectionDemo() { const [countries, setCountries] = React.useState([]); const [filteredCountries, setFilteredCountries] = React.useState([]); React.useEffect(() => { CountryService.getCountries().then((data) => setCountries(data)); }, []); const search = (event: AutoCompleteCompleteEvent) => { if (!event.query.trim().length) { setFilteredCountries(countries); } else { setFilteredCountries( countries.filter((country) => { return country.name.toLowerCase().startsWith(event.query.toLowerCase()); }) ); } }; return (
No countries found
); } ``` ### Group #### Simple Options can be grouped using the `optionGroupLabel` and `optionGroupChildren` properties. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; interface Technology { label: string; value: string; } interface TechCategory { label: string; code: string; items: Technology[]; } const techStack: TechCategory[] = [ { label: 'Frontend', code: 'FE', items: [ { label: 'React', value: 'react' }, { label: 'Vue', value: 'vue' }, { label: 'Angular', value: 'angular' }, { label: 'Svelte', value: 'svelte' } ] }, { label: 'Backend', code: 'BE', items: [ { label: 'Node.js', value: 'nodejs' }, { label: 'Python', value: 'python' }, { label: 'Java', value: 'java' }, { label: 'Go', value: 'go' } ] }, { label: 'Database', code: 'DB', items: [ { label: 'PostgreSQL', value: 'postgresql' }, { label: 'MongoDB', value: 'mongodb' }, { label: 'Redis', value: 'redis' }, { label: 'MySQL', value: 'mysql' } ] } ]; const filterItems = (items: Technology[], query: string): Technology[] => { if (!query) return items; const normalizedQuery = query.toLowerCase(); return items.filter((item) => item.label.toLowerCase().includes(normalizedQuery)); }; export default function GroupDemo() { const [filteredTech, setFilteredTech] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query; const _filteredTech: TechCategory[] = []; for (const category of techStack) { const filteredItems = filterItems(category.items, query); if (filteredItems.length) { _filteredTech.push({ ...category, items: filteredItems }); } } setFilteredTech(_filteredTech); }; return (
No option found
); } ``` #### Custom Customize group headers with custom rendering by accessing the list instance inside `AutoComplete.List`. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompleteOption, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import type { ListboxListInstance } from 'primereact/listbox'; import * as React from 'react'; interface City { label: string; value: string; } interface CityGroup { label: string; code: string; items: City[]; } const groupedCities: CityGroup[] = [ { label: 'Germany', code: 'DE', items: [ { label: 'Berlin', value: 'Berlin' }, { label: 'Frankfurt', value: 'Frankfurt' }, { label: 'Hamburg', value: 'Hamburg' } ] }, { label: 'USA', code: 'US', items: [ { label: 'Chicago', value: 'Chicago' }, { label: 'Los Angeles', value: 'Los Angeles' }, { label: 'New York', value: 'New York' } ] }, { label: 'Japan', code: 'JP', items: [ { label: 'Kyoto', value: 'Kyoto' }, { label: 'Osaka', value: 'Osaka' }, { label: 'Tokyo', value: 'Tokyo' } ] } ]; const countryFlags: Record = { DE: '\uD83C\uDDE9\uD83C\uDDEA', US: '\uD83C\uDDFA\uD83C\uDDF8', JP: '\uD83C\uDDEF\uD83C\uDDF5' }; export default function CustomGroupDemo() { const [filteredCities, setFilteredCities] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { const query = event.query.toLowerCase(); const filtered: CityGroup[] = []; for (const group of groupedCities) { const filteredItems = query ? group.items.filter((item) => item.label.toLowerCase().includes(query)) : group.items; if (filteredItems.length) { filtered.push({ ...group, items: filteredItems }); } } setFilteredCities(filtered); }; return (
{(instance: ListboxListInstance) => { const { listbox, options } = instance; return (options as unknown[])?.map((option: unknown, index: number) => { const isGroup = listbox?.isOptionGroup(option); if (isGroup) { const optionGroup = (option as Record).optionGroup as Record; const code = optionGroup?.code; return ( {countryFlags[code]} {optionGroup?.label} ); } const label = listbox?.getOptionLabel(option); return ( {label} ); }); }} No cities found
); } ``` ### Arrow Use `AutoComplete.Arrow` inside the popup to display an arrow pointing to the trigger element. Set `sideOffset` on `AutoComplete.Positioner` for spacing. ```tsx 'use client'; import { AutoComplete, AutoCompleteArrow, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function ArrowDemo() { const [items, setItems] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
); } ``` ### Fluid Use the `fluid` property to make the autocomplete take up the full width of its container. ```tsx 'use client'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function FluidDemo() { const [items, setItems] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
); } ``` ### Loading Compose a loading indicator alongside the autocomplete input using `InputGroup` to display an async loading state. ```tsx 'use client'; import { Spinner } from '@primeicons/react'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import { InputGroup, InputGroupAddon } from '@/components/ui/inputgroup'; import * as React from 'react'; export default function LoadingDemo() { const [items, setItems] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
); } ``` ### Sizes Use the `size` property to change the size of an autocomplete. ```tsx 'use client'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function SizesDemo() { const [items, setItems] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
); } ``` ### Filled Specify the `variant="filled"` property to display the component with a higher visual emphasis than the default outlined style. ```tsx 'use client'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function FilledDemo() { const [items, setItems] = React.useState([]); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
); } ``` ### Disabled Use the `disabled` property to disable an autocomplete. ```tsx import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; export default function DisabledDemo() { return (
); } ``` ### Invalid Specify the `invalid` property to display the component with a red border for validation errors. ```tsx 'use client'; import { AutoComplete, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import type { AutoCompleteCompleteEvent, AutoCompleteValueChangeEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function InvalidDemo() { const [items, setItems] = React.useState([]); const [value, setValue] = React.useState(null); const [value2, setValue2] = React.useState(null); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
setValue(e.value as string)} className="w-full md:w-56" invalid={!value} > setValue2(e.value as string)} className="w-full md:w-56" invalid={!value2} >
); } ``` ### Focus Behavior Customize keyboard and mouse focus behavior with `autoOptionFocus`, `selectOnFocus`, and `focusOnHover` props. ```tsx 'use client'; import { AutoComplete, AutoCompleteEmpty, AutoCompleteInput, AutoCompleteList, AutoCompletePopup, AutoCompletePortal, AutoCompletePositioner } from '@/components/ui/autocomplete'; import { Button } from '@/components/ui/button'; import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete'; import * as React from 'react'; export default function FocusBehaviorDemo() { const [items, setItems] = React.useState([]); const [autoOptionFocus, setAutoOptionFocus] = React.useState(true); const [selectOnFocus, setSelectOnFocus] = React.useState(false); const [focusOnHover, setFocusOnHover] = React.useState(true); const search = (event: AutoCompleteCompleteEvent) => { setItems([...Array(10).keys()].map((item) => event.query + '-' + item)); }; return (
No cities found
); } ``` ## Related ### Sub-Components See [Primitive API](/docs/primitive/components/autocomplete#api) for `AutoCompleteRoot`, `AutoCompleteInput`, `AutoCompleteTrigger`, `AutoCompleteList`, `AutoCompleteOption`, and other component documentation. ### Hooks See [Headless API](/docs/headless/components/autocomplete#api) for `useAutoComplete` hook documentation. ### Accessibility See [AutoComplete Primitive](/docs/primitive/components/autocomplete#accessibility) for WAI-ARIA compliance details and keyboard support. # Avatar Avatar represents people using icons, labels and images. ```tsx import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Divider } from '@/components/ui/divider'; export default function Preview() { return (
A
Amy Elsner London to Miami LHR → MIA
14:30 Gate B12
OL
Onyama Limba Paris to Istanbul CDG → IST
09:15 Gate A4
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/avatar.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; ``` ```tsx CC ``` ## Examples ### Basic ```tsx import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; export default function BasicDemo() { return (
A
); } ``` ### Fallback The `Avatar.Fallback` component displays a label or an icon when an image fails to load or when an image is not preferred. ```tsx import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Check, User } from '@primeicons/react'; export default function LabelDemo() { return (
J CC
); } ``` ### Image The `Avatar.Image` component displays an image as an Avatar. ```tsx import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; export default function ImageDemo() { return (
A A O
); } ``` ### Badge [`Badge`](/docs/components/badge) component can be used to display a badge on an Avatar. ```tsx import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { OverlayBadge } from '@/components/ui/overlaybadge'; const BadgeDemo = () => { return (
O 2 W
); }; export default BadgeDemo; ``` ### Shape Use the `shape` property to change the appearance. ```tsx import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; const ShapeDemo = () => { return (
W W
); }; export default ShapeDemo; ``` ### Sizes Use the `size` property to change the size of an avatar. ```tsx import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; export default function SizeDemo() { return (
AB AB AB
AB AB AB
); } ``` ### AvatarGroup Grouping is available by wrapping multiple Avatar components inside an `Avatar.Group` component. ```tsx import { Avatar, AvatarImage, AvatarFallback, AvatarGroup } from '@/components/ui/avatar'; export default function GroupDemo() { return (
A A O I X +2
); } ``` ## Accessibility ### Screen Reader Avatar does not include any roles and attributes by default. Any attribute is passed to the root element so you may add a role like img along with aria-labelledby or aria-label to describe the component. In case avatars need to be tabbable, tabindex can be added as well to implement custom key handlers. ### Keyboard Support Component does not include any interactive elements. # Badge Badge is a small status indicator for another element. ```tsx import { Bell, Check, Clock, ExclamationTriangle, Lock, StarFill, Tag, TimesCircle } from '@primeicons/react'; import { Badge } from '@/components/ui/badge'; export default function Preview() { return (
Approved Rejected Warning Pending Draft Featured 3 New Online Private Security issue
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/badge.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Badge } from '@/components/ui/badge'; ``` ```tsx Badge ``` ## Examples ### Basic ```tsx import { Badge } from '@/components/ui/badge'; export default function BasicDemo() { return (
Badge
); } ``` ### Severity The `severity` property defines the visual style of a badge. ```tsx import { Badge } from '@/components/ui/badge'; export default function SeverityDemo() { return (
Default Secondary Success Info Warning Danger Contrast
); } ``` ### Size Use the `size` property to change the size of a badge. ```tsx import { Badge } from '@/components/ui/badge'; export default function SizeDemo() { return (
Small Default Large XLarge
); } ``` ### Overlay A badge can be added to any element by encapsulating the content with the `OverlayBadge` component. ```tsx import { Badge } from '@/components/ui/badge'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Bell, Calendar, Envelope } from '@primeicons/react'; export default function BasicDemo() { return (
2 4
); } ``` ### Button Buttons have built-in support for badges to display a badge inline. ```tsx import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Bell, Users } from '@primeicons/react'; export default function ButtonDemo() { return (
); } ``` ## Accessibility ### Screen Reader Badge does not include any roles and attributes by default, any attribute is passed to the root element so aria roles and attributes can be added if required. If the badges are dynamic, _aria-live_ may be utilized as well. In case badges need to be tabbable, _tabindex_ can be added to implement custom key handlers. ### Keyboard Support Component does not include any interactive elements. # Breadcrumb Breadcrumb provides contextual information about page hierarchy. ```tsx import { Bolt, Home } from '@primeicons/react'; import { Breadcrumb, BreadcrumbCurrent, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; export default function Preview() { return (
Products Electronics Laptops Dell
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/breadcrumb.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; ``` ```tsx Home Current Page ``` ## Examples ### Basic ```tsx import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; export default function BasicDemo() { return (
Home Products Electronics Laptops Dell
); } ``` ### Route A breadcrumb can be used with routing libraries to navigate between pages. ```tsx 'use client'; import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; import * as React from 'react'; const allPages = [ { label: 'Home' }, { label: 'Components' }, { label: 'Form' }, { label: 'Input' }, { label: 'InputText' }, { label: 'Variants' }, { label: 'Filled' }, { label: 'Outlined' } ]; export default function RouteDemo() { const [pages, setPages] = React.useState(allPages); const handleClick = (index: number) => { setPages(pages.slice(0, index + 1)); }; return (
{pages.map((page, index) => ( {index > 0 && } {index === pages.length - 1 ? ( {page.label} ) : ( handleClick(index)}> {page.label} )} ))}
); } ``` ### Controlled A breadcrumb can be controlled by managing the current page state. ```tsx 'use client'; import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; import * as React from 'react'; export default function ControlledDemo() { const [currentPage, setCurrentPage] = React.useState('reports'); const pages = [ { key: 'home', label: 'Home' }, { key: 'users', label: 'Users' }, { key: 'documents', label: 'Documents' }, { key: 'work', label: 'Work' }, { key: 'reports', label: 'Reports' } ]; return (
{pages.map((page, index) => ( {index > 0 && } {currentPage === page.key ? ( {page.label} ) : ( setCurrentPage(page.key)}> {page.label} )} ))}
); } ``` ### Custom Separator A breadcrumb allows customization of the separator between items. ```tsx import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; export default function CustomSeparatorDemo() { return (
Home / Products / Electronics / Laptops / Dell
); } ``` ### Ellipsis ```tsx import { Breadcrumb, BreadcrumbCurrent, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; export default function EllipsisDemo() { return (
Home Electronics Laptops Dell
); } ``` ### Custom Item A breadcrumb allows customization of the items. ```tsx 'use client'; import { Badge } from '@/components/ui/badge'; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; import { Check, ChevronDown } from '@primeicons/react'; import { Menu } from '@primereact/ui/menu'; import * as React from 'react'; export default function CustomItemDemo() { const [selectedBrand, setSelectedBrand] = React.useState('Apple'); const brands = [{ label: 'Apple' }, { label: 'Dell' }, { label: 'HP' }, { label: 'Lenovo' }, { label: 'Asus' }]; return (
Home Products Electronics Computers Laptops 5 {selectedBrand} Select Brand {brands.map((brand) => ( setSelectedBrand(brand.label)} > {brand.label} ))}
); } ``` # Button Button is an extension to standard input element with icons and theming. ```tsx import { Button } from '@/components/ui/button'; export default function Preview() { return (
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/button.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Button } from '@/components/ui'; ``` ```tsx ``` ## Examples ### Basic ```tsx import { Button } from '@/components/ui/button'; export default function BasicDemo() { return (
); } ``` ### Icons ```tsx import { Button } from '@/components/ui/button'; import { Check, Home, Refresh, Search, User } from '@primeicons/react'; export default function IconDemo() { return (
); } ``` ### Loading ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { useState } from 'react'; import { Check, Spinner } from '@primeicons/react'; export default function LoadingDemo() { const [loading, setLoading] = useState(false); const load = () => { setLoading(true); setTimeout(() => { setLoading(false); }, 2000); }; return (
); } ``` ### As Link Use `as="a"` to render a button as HTML anchor tag or use `as={Link}` to use Next.js Link. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import Link from 'next/link'; export default function LinkDemo() { return (
); } ``` ### Severity The `severity` property defines the variant of a button. ```tsx import { Button } from '@/components/ui/button'; export default function SeverityDemo() { return (
); } ``` ### Raised Raised buttons display a shadow to indicate elevation. ```tsx import { Button } from '@/components/ui/button'; export default function RaisedDemo() { return (
); } ``` ### Rounded Rounded buttons have a circular border radius. ```tsx import { Button } from '@/components/ui/button'; export default function RoundedDemo() { return (
); } ``` ### Text Text buttons are displayed as textual elements. ```tsx import { Button } from '@/components/ui/button'; export default function TextDemo() { return (
); } ``` ### Raised Text Text buttons can be displayed elevated with the `raised` option. ```tsx import { Button } from '@/components/ui/button'; export default function RaisedTextDemo() { return (
); } ``` ### Outlined Outlined buttons display a border without a transparent background. ```tsx import { Button } from '@/components/ui/button'; export default function OutlinedDemo() { return (
); } ``` ### Icon Only Buttons can have icons without labels. Use `iconOnly` to display only an icon. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { useState } from 'react'; import { Bell, Bookmark, Check, Heart, Search, Star, Times, User } from '@primeicons/react'; export default function IconOnlyDemo() { /*const sizeOptions = useRef([ { label: 'Small', value: 'small' }, { label: 'Normal', value: 'normal' }, { label: 'Large', value: 'large' } ]);*/ const [size] = useState<'small' | 'normal' | 'large'>('normal'); return (
{/**/}
); } ``` ### Badge `Badge` component can be used to display a badge inside a button. `OverlayBadge` is used to display a badge on a button. ```tsx import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Bell, Users } from '@primeicons/react'; export default function BadgeDemo() { return (
); } ``` ### Sizes Button provides `small` and `large` sizes as alternatives to the base. ```tsx import { Button } from '@/components/ui/button'; import { Check } from '@primeicons/react'; export default function SizeDemo() { return (
); } ``` ### Disabled When `disabled` is present, the element cannot be used. ```tsx import { Button } from '@/components/ui/button'; export default function DisabledDemo() { return (
); } ``` # ButtonGroup ButtonGroup composes related buttons into a connected pill with shared corners and seams. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Menu, MenuItem, MenuList, MenuPopup, MenuPortal, MenuPositioner, MenuTrigger } from '@/components/ui/menu'; import { Check, ChevronDown, ChevronLeft, ChevronRight, Clock, File, Send, Times, Trash } from '@primeicons/react'; export default function Preview() { return (
Save as draft Save and publish Save and schedule
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/buttongroup.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; ``` ```tsx ``` ## Examples ### Basic A standard ButtonGroup wraps multiple buttons into a connected unit. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Check, Times, Trash } from '@primeicons/react'; export default function BasicDemo() { return (
); } ``` ### Icons Buttons inside a group support leading and trailing icons. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Check, Refresh, Search, User } from '@primeicons/react'; export default function IconDemo() { return (
); } ``` ### Severity Each button in a group can use a different severity to convey intent. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronDown, Check } from '@primeicons/react'; const severities = ['primary', 'secondary', 'success', 'info', 'warn', 'help', 'danger', 'contrast'] as const; export default function SeverityDemo() { return (
{severities.map((severity) => ( ))}
); } ``` ### Raised Raised buttons display a shadow to indicate elevation. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronDown, Check } from '@primeicons/react'; const severities = ['primary', 'secondary', 'success', 'info', 'warn', 'help', 'danger', 'contrast'] as const; export default function RaisedDemo() { return (
{severities.map((severity) => ( ))}
); } ``` ### Rounded Rounded buttons soften the outer corners of the group. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronDown, Check } from '@primeicons/react'; const severities = ['primary', 'secondary', 'success', 'info', 'warn', 'help', 'danger', 'contrast'] as const; export default function RoundedDemo() { return (
{severities.map((severity) => ( ))}
); } ``` ### Text Text buttons render as textual elements while keeping the connected layout. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronDown, Check } from '@primeicons/react'; const severities = ['primary', 'secondary', 'success', 'info', 'warn', 'help', 'danger', 'contrast'] as const; export default function TextDemo() { return (
{severities.map((severity) => ( ))}
); } ``` ### Outlined Outlined buttons display a border without a transparent background. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronDown, Check } from '@primeicons/react'; const severities = ['primary', 'secondary', 'success', 'info', 'warn', 'help', 'danger', 'contrast'] as const; export default function OutlinedDemo() { return (
{severities.map((severity) => ( ))}
); } ``` ### Icon Only Icon-only buttons can be combined into compact toolbars and pickers. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { ChevronLeft, ChevronRight } from '@primeicons/react'; const variants = [ { key: 'default', props: {} }, { key: 'outlined', props: { variant: 'outlined' as const } }, { key: 'text', props: { variant: 'text' as const } }, { key: 'raised', props: { raised: true } }, { key: 'rounded', props: { rounded: true } } ]; export default function IconOnlyDemo() { return (
{variants.map(({ key, props }) => ( ))}
{variants.map(({ key, props }) => ( ))}
); } ``` ### Sizes ButtonGroup supports `small` and `large` sizes alongside the default. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Check, Times, Trash } from '@primeicons/react'; export default function SizeDemo() { return (
); } ``` ### Disabled When `disabled` is present on each button, the group becomes non-interactive. ```tsx import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Check, Times, Trash } from '@primeicons/react'; export default function DisabledDemo() { return (
); } ``` ### Split Button Pair a primary action with a `MenuTrigger` button to expose related options without crowding the main action. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/buttongroup'; import { Menu, MenuSubmenuIndicator, MenuItem, MenuList, MenuPopup, MenuPortal, MenuPositioner, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger } from '@/components/ui/menu'; import { Check, ChevronDown, ChevronRight, Clock, File, Send } from '@primeicons/react'; export default function SplitButtonDemo() { return (
Save as draft Save and publish Save and schedule Download as PDF Word Markdown HTML Send via email Copy link
); } ``` # Card Card is a flexible container component. ```tsx import { Button } from '@/components/ui/button'; import { Card, CardBody, CardCaption, CardContent, CardFooter, CardSubTitle, CardTitle } from '@/components/ui/card'; export default function Preview() { return ( Pro Subscription Everything you need to scale your workflow

Get unlimited access to all features, priority support, and advanced analytics to help your team grow faster.

$29 / month
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/card.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Card, CardBody, CardCaption, CardContent, CardFooter, CardSubTitle, CardTitle } from '@/components/ui/card'; ``` ```tsx Title Subtitle ``` ## Examples ### Basic ```tsx import { Card, CardBody, CardCaption, CardContent, CardFooter, CardSubTitle, CardTitle } from '@/components/ui/card'; export default function BasicDemo() { return ( Starter Plan For personal projects

Includes essential features, basic analytics and access to community support.

Cancel anytime. No credit card required.
); } ``` ### With Form Use `Card`, `Card.Body`, `Card.Caption`, `Card.Title`, `Card.Subtitle`, `Card.Content`, `Card.Footer`, to create a simple card. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { Card, CardBody, CardCaption, CardContent, CardFooter, CardSubTitle, CardTitle } from '@/components/ui/card'; import { InputText } from '@/components/ui/inputtext'; import { Label } from '@/components/ui/label'; import Link from 'next/link'; export default function WithFormDemo() { return ( Welcome back Sign in with your email to continue.
Don't have an account?{' '}
); } ``` ### Advanced Use `Card.Header` to place an image, avatar or other content in the header. ```tsx import { Globe, MapMarker, Phone, StarFill } from '@primeicons/react'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { Card, CardBody, CardCaption, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Tag } from '@/components/ui/tag'; export default function AdvancedDemo() { return ( user header Sakura Fresh Market
Daily Premium

Sakura Fresh Market is your go-to store for fresh local produce, Japanese snacks, and daily essentials — all in one place!

4.6 (200+ reviews)
Tokyo, Shibuya-ku
); } ``` ## Accessibility ### Screen Reader `Card` is a structural container. If the card represents a standalone content region, provide an accessible name using `aria-label` or `aria-labelledby` on `Card.Root`. ### Keyboard Support `Card` does not include built-in keyboard interaction. Keyboard behavior depends on interactive elements placed inside the card (for example buttons, links, or form controls). # Carousel Carousel is a content slider featuring various customization options. ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrev } from '@/components/ui/carousel'; const albums = [ { title: 'Velvet Haze', artist: 'Aurora Skies', img: 'https://plus.unsplash.com/premium_photo-1721310985165-4e6e63d5e7a1?w=200&h=200&fit=crop&auto=format' }, { title: 'Dissolve', artist: 'Phantom Echo', img: 'https://images.unsplash.com/photo-1711054824441-064a99073a0b?w=200&h=200&fit=crop&auto=format' }, { title: 'Still Life', artist: 'Glass Animals', img: 'https://images.unsplash.com/photo-1651443039959-582bbea6be6a?w=200&h=200&fit=crop&auto=format' }, { title: 'Bloom', artist: 'Petal Storm', img: 'https://plus.unsplash.com/premium_photo-1748180936767-e1ce9b2374fa?w=200&h=200&fit=crop&auto=format' }, { title: 'Refraction', artist: 'Prism Collective', img: 'https://images.unsplash.com/photo-1651443146979-4cf9a27dcade?w=200&h=200&fit=crop&auto=format' }, { title: 'Ivory', artist: 'Soft Landing', img: 'https://images.unsplash.com/photo-1531777992189-ad52457fbe93?w=200&h=200&fit=crop&auto=format' } ]; export default function Preview() { return (
Last Played
{albums.map((album) => (
{album.title}
{album.title}
{album.artist}
))}
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/carousel.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Carousel, CarouselContent, CarouselIndicators, CarouselItem, CarouselNext, CarouselPrev } from '@/components/ui/carousel'; ``` ```tsx ... ``` ## Examples ### Basic ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselIndicators, CarouselPrev, CarouselNext } from '@/components/ui/carousel'; function BasicDemo() { return (
{Array.from({ length: 5 }).map((_, i) => (
{i + 1}
))}
); } export default BasicDemo; ``` ### Alignment Use the `align` property to align the carousel items. `slidesPerPage` property is used to control the number of slides visible per page. ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselIndicators, CarouselPrev, CarouselNext } from '@/components/ui/carousel'; function AlignmentDemo() { return (
{Array.from({ length: 5 }).map((_, i) => (
{i + 1}
))}
); } export default AlignmentDemo; ``` ### Orientation Use the `orientation` property to change the orientation of the carousel. ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselPrev, CarouselNext } from '@/components/ui/carousel'; function SizeDemo() { return (
{Array.from({ length: 5 }).map((_, i) => (
{i + 1}
))}
); } export default SizeDemo; ``` ### Loop Use the `loop` property to enable loop mode. ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselIndicators, CarouselPrev, CarouselNext } from '@/components/ui/carousel'; function LoopDemo() { return (
{Array.from({ length: 5 }).map((_, i) => (
{i + 1}
))}
); } export default LoopDemo; ``` ### Variable Size Use the `autoSize` property to enable auto size mode and set the width of the carousel items. ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselIndicators, CarouselPrev, CarouselNext } from '@/components/ui/carousel'; const items = ['120px', '80px', '200px', '160px', '220px', '180px', '280px', '100px']; function VariableSizeDemo() { return (
{items.map((width, i) => (
{i + 1}
))}
); } export default VariableSizeDemo; ``` ### Gallery Gallery demo shows how to use the `slide` property to scroll to a specific slide by index. ```tsx 'use client'; import { Carousel, CarouselContent, CarouselItem } from '@/components/ui/carousel'; import type { CarouselRootChangeEvent } from 'primereact/carousel'; import * as React from 'react'; const images = [ 'https://images.unsplash.com/photo-1589656966895-2f33e7653819?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1518717758536-85ae29035b6d?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1704905832963-37d6f12654b7?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1470130623320-9583a8d06241?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1678841446310-d045487ef299?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1497752531616-c3afd9760a11?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1511885663737-eea53f6d6187?q=80&w=1374&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1598439210625-5067c578f3f6?q=80&w=1472&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', 'https://images.unsplash.com/photo-1638255402906-e838358069ab?q=80&w=1631&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ]; function GalleryDemo() { const [selectedImage, setSelectedImage] = React.useState(0); return (
setSelectedImage(Number(e.value ?? 0))} align="center"> {images.map((_, i) => ( {`Image ))} {images.map((_, i) => ( setSelectedImage(i)} className={`cursor-pointer basis-1/4! transition-opacity ${selectedImage === i ? '' : 'opacity-60 hover:opacity-40'}`} > {`Image ))}
); } export default GalleryDemo; ``` # Checkbox Checkbox is an extension to standard checkbox element with theming. ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; export default function Preview() { return (
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/checkbox.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Checkbox } from '@/components/ui/checkbox'; ``` ```tsx ``` ## Examples ### Basic ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; const BasicDemo = () => { return (
); }; export default BasicDemo; ``` ### Indeterminate Use the `indeterminate` property to display an indeterminate state. ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; export default function IndeterminateDemo() { return (
); } ``` ### Sizes Use the `size` property to change the size of a checkbox. ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; export default function SizesDemo() { return (
); } ``` ### Filled Specify the `filled` property to display the component with a higher visual emphasis than the default outlined style. ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; export default function FilledDemo() { return (
); } ``` ### Disabled Use the `disabled` property to disable a checkbox. ```tsx import { Checkbox } from '@/components/ui/checkbox'; export default function DisabledDemo() { return (
); } ``` ### Invalid Specify the `invalid` property to display the component with a red border. ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; export default function InvalidDemo() { return (
); } ``` # CheckboxGroup Checkbox is an extension to standard checkbox element with theming. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; const options = [ { label: 'Gemini', value: 'gemini' }, { label: 'Claude', value: 'claude' }, { label: 'ChatGPT', value: 'chatgpt' }, { label: 'Deepseek', value: 'deepseek' } ]; export default function Preview() { return (
Survey
Which LLM provider should we integrate next?
{options.map((option) => (
))}
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/checkboxgroup.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; ``` ```tsx ``` ## Examples ### Basic ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; import { Label } from '@/components/ui/label'; export default function BasicDemo() { return (
); } ``` ### Controlled Drive the selection with `value` and `onValueChange` to keep it in your own state. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; import type { CheckboxGroupChangeEvent } from 'primereact/checkboxgroup'; import { Label } from '@/components/ui/label'; import * as React from 'react'; const ingredients = ['Cheese', 'Mushroom', 'Pepper', 'Onion']; export default function ControlledDemo() { const [value, setValue] = React.useState(['Cheese']); return (
onValueChange: {JSON.stringify(value)}
setValue(e.value as string[])} className="gap-4 flex-wrap"> {ingredients.map((ingredient) => (
))}
); } ``` ### Dynamic Checkboxes can be generated using a list of values. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; import { Label } from '@/components/ui/label'; export default function DynamicDemo() { const categories = [ { name: 'Accounting', key: 'A' }, { name: 'Marketing', key: 'M' }, { name: 'Production', key: 'P' }, { name: 'Research', key: 'R' } ]; return (
{categories.map((category) => (
))}
); } ``` ### Card Checkboxes can be displayed in a card format. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; const interests = [ { id: 'tech', title: '💻 Technology', description: 'Latest updates in software, gadgets, and innovation.' }, { id: 'design', title: '🎨 Design', description: 'UI/UX trends, graphic design tips, and creativity.' }, { id: 'finance', title: '💰 Finance', description: 'Investing, saving, and crypto news.' } ]; export default function CardDemo() { return (
Select your interests:
{interests.map((interest) => ( ))}
); } ``` ### Indeterminate Use the `indeterminate` property to display an indeterminate state. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { CheckboxGroup } from '@/components/ui/checkboxgroup'; import type { CheckboxRootChangeEvent } from 'primereact/checkbox'; import type { CheckboxGroupChangeEvent } from 'primereact/checkboxgroup'; import { Label } from '@/components/ui/label'; import React from 'react'; const categories = [ { name: 'Product updates', key: 'product-updates' }, { name: 'Weekly newsletter', key: 'weekly-newsletter' }, { name: 'Security alerts', key: 'security-alerts' } ]; export default function IndeterminateDemo() { const [value, setValue] = React.useState([]); const isAllSelected = categories.every((category) => value.includes(category.key)); const indeterminate = categories.some((category) => value.includes(category.key)) && !isAllSelected; return (
setValue(e.checked ? categories.map((category) => category.key) : [])} />
setValue(event.value as string[])} className="flex-col gap-4 pl-7" > {categories.map((item) => (
))}
); } ``` # Chip Chip represents entities using icons, labels and images. ```tsx 'use client'; import { Check } from '@primeicons/react'; import { Chip, ChipLabel, ChipStart } from '@/components/ui/chip'; import * as React from 'react'; const chips = [ { label: 'Music', emoji: '🎵' }, { label: 'Movies', emoji: '🎬' }, { label: 'Travel', emoji: '✈️' }, { label: 'Food', emoji: '🍔' }, { label: 'Sports', emoji: '⚽' }, { label: 'Gaming', emoji: '🎮' }, { label: 'Reading', emoji: '📚' }, { label: 'Photography', emoji: '📸' }, { label: 'Fitness', emoji: '💪' }, { label: 'Technology', emoji: '💻' }, { label: 'Art', emoji: '🎨' }, { label: 'Nature', emoji: '🌿' }, { label: 'Shopping', emoji: '🛍️' }, { label: 'Cooking', emoji: '👨‍🍳' }, { label: 'Pets', emoji: '🐶' }, { label: 'Cars', emoji: '🚗' }, { label: 'Fashion', emoji: '👕' }, { label: 'Science', emoji: '🔬' } ]; export default function Preview() { const [selectedIndex, setSelectedIndex] = React.useState([0, 4, 7, 10, 15]); const onSelect = (index: number) => { if (selectedIndex.includes(index)) { setSelectedIndex((prev) => prev.filter((i) => i !== index)); } else { setSelectedIndex((prev) => [...prev, index]); } }; return (

What are you interested in?

You can select multiple answers.

{chips.map((chip, index) => { const isSelected = selectedIndex.includes(index); return ( onSelect(index)} variant={isSelected ? undefined : 'outlined'} className="cursor-pointer" > {isSelected ? ( ) : ( chip.emoji )} {chip.label} ); })}
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/chip.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Chip, ChipLabel, ChipStart, ChipEnd, ChipRemove } from '@/components/ui/chip'; ``` ```tsx Label ``` ## Examples ### Basic Displays compact information with an optional remove action. ```tsx import { Github } from '@primeicons/react'; import { Chip, ChipLabel, ChipStart } from '@/components/ui/chip'; export default function BasicDemo() { return (
GitHub
); } ``` ### Start & End Elements ```tsx import { Apple, Facebook, Github, Google, Microsoft, MinusCircle } from '@primeicons/react'; import { Chip, ChipEnd, ChipLabel, ChipRemove, ChipStart } from '@/components/ui/chip'; export default function StartEndDemo() { return (
Apple Facebook Google Microsoft GitHub
); } ``` ### With Avatar ```tsx import { TimesCircle } from '@primeicons/react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Chip, ChipEnd, ChipLabel, ChipRemove, ChipStart } from '@/components/ui/chip'; export default function WithAvatarDemo() { return (
AE Amy Elsner AJ Asiya Javayant Onyama Limba OL XF Xuxue Feng
); } ``` ### Template Chip also allows displaying custom content inside itself. ```tsx import { Apple, Facebook, Github, Google, Microsoft, MinusCircle } from '@primeicons/react'; import { Chip, ChipEnd, ChipLabel, ChipRemove, ChipStart } from '@/components/ui/chip'; export default function TemplateDemo() { return (
Apple Facebook Google Microsoft GitHub
); } ``` ## Related ### Sub-Components See [Primitive API](/docs/primitive/components/chip#api) for `ChipRoot`, `ChipStart`, `ChipLabel`, `ChipEnd`, and `ChipRemove` component documentation. ### Hooks See [Headless API](/docs/headless/components/chip#api) for `useChip` hook documentation. ### Accessibility See [Chip Primitive](/docs/primitive/components/chip#accessibility) for WAI-ARIA compliance details and keyboard support. # Compare Compare is a component to compare two images with a slider. ```tsx import { Code } from '@primeicons/react'; import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; export default function Preview() { return ( ); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/compare.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; ``` ```tsx ``` ## Examples ### Basic Compare two items side by side with a slider. ```tsx import { Code } from '@primeicons/react'; import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; export default function BasicDemo() { return ( ); } ``` ### Hover ```tsx import { Code } from '@primeicons/react'; import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; export default function HoverDemo() { return ( ); } ``` ### Vertical ```tsx import { Code } from '@primeicons/react'; import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; export default function VerticalDemo() { return ( ); } ``` ### With Chart ```tsx 'use client'; import { Compare, CompareItem } from '@/components/ui/compare'; import type { CompareRootValueChangeEvent } from 'primereact/compare'; import * as React from 'react'; export default function ChartDemo() { const [value, setValue] = React.useState(50); return ( setValue(e.value as number)} onPointerLeave={() => setValue(100)} > ); } ``` ### Template ```tsx import { Avatar, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { Compare, CompareHandle, CompareIndicator, CompareItem } from '@/components/ui/compare'; import { ProgressBar } from '@/components/ui/progressbar'; import { Tag } from '@/components/ui/tag'; import { Code } from '@primeicons/react'; export default function TemplateDemo() { return ( ); } function ThemePanel({ theme = 'purple' }: { theme: 'purple' | 'primary' }) { const t = { purple: { bg: 'bg-purple-100 dark:bg-purple-900/50', card: 'bg-white dark:bg-purple-900 border-purple-100 dark:border-purple-800', text: 'text-purple-950 dark:text-purple-50', muted: 'text-purple-600 dark:text-purple-400', tag: 'bg-purple-100 dark:bg-purple-800 text-purple-700 dark:text-purple-200', track: 'bg-purple-100 dark:bg-purple-800', indicator: 'bg-purple-500', avatar: 'filter hue-rotate-[260deg] saturate-150', severity: 'help' as const }, primary: { bg: 'bg-primary-100 dark:bg-primary-900/50', card: 'bg-white dark:bg-primary-900 border-primary-100 dark:border-primary-800', text: 'text-primary-950 dark:text-primary-50', muted: 'text-primary-600 dark:text-primary-400', tag: 'bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-200', track: 'bg-primary-100 dark:bg-primary-800', indicator: 'bg-primary', severity: undefined } }[theme]; return (
Amy Elsner
Developer
Pro
Storage 7.2 GB / 10 GB
); } ``` ## Accessibility ### Screen Reader Compare component uses a hidden range input for keyboard accessibility. Use `ariaLabel` to provide a descriptive label. ### Keyboard Support | Key | Function | | ------------- | ------------------------------------------------- | | `left arrow` | Moves the comparison slider to the left. | | `right arrow` | Moves the comparison slider to the right. | | `home` | Moves the comparison slider to the minimum value. | | `end` | Moves the comparison slider to the maximum value. | # ContextMenu A right-click triggered menu with keyboard navigation, nested submenus, and WAI-ARIA support. ```tsx 'use client'; import { Bookmark } from '@primeicons/react/bookmark'; import { ChevronRight } from '@primeicons/react/chevron-right'; import { Clipboard } from '@primeicons/react/clipboard'; import { Copy } from '@primeicons/react/copy'; import { Download } from '@primeicons/react/download'; import { ExternalLink } from '@primeicons/react/external-link'; import { Eye } from '@primeicons/react/eye'; import { FileEdit } from '@primeicons/react/file-edit'; import { Link } from '@primeicons/react/link'; import { Pencil } from '@primeicons/react/pencil'; import { Trash } from '@primeicons/react/trash'; import { ContextMenu, ContextMenuGroup, ContextMenuSubmenuIndicator, ContextMenuItem, ContextMenuLabel, ContextMenuList, ContextMenuPopup, ContextMenuPortal, ContextMenuPositioner, ContextMenuSeparator, ContextMenuSubmenu, ContextMenuSubmenuTrigger, ContextMenuTrigger } from '@/components/ui/contextmenu'; export default function Preview() { return ( Right-click here Edit Cut Copy Paste Rename Share Copy link Open in new tab Download Add to favorites Preview Delete ); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/contextmenu.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { ContextMenu, ContextMenuArrow, ContextMenuIndicator, ContextMenuItem, ContextMenuLabel, ContextMenuList, ContextMenuPopup, ContextMenuPortal, ContextMenuPositioner, ContextMenuRadioGroup, ContextMenuSeparator, ContextMenuSubmenu, ContextMenuSubmenuTrigger, ContextMenuTrigger } from '@/components/ui/contextmenu'; ``` ```tsx
Right-click here
Copy Paste Delete
``` ## Examples ### Basic A standard context menu opened with a right-click on the trigger area. ```tsx 'use client'; import { ContextMenu, ContextMenuGroup, ContextMenuItem, ContextMenuList, ContextMenuPopup, ContextMenuPortal, ContextMenuPositioner, ContextMenuSeparator, ContextMenuTrigger } from '@/components/ui/contextmenu'; export default function BasicDemo() { return ( Right-click here Cut Copy Paste Rename Delete ); } ``` ### Submenus Nest a `ContextMenuSubmenu` inside any `ContextMenuList`. Reuse the same `ContextMenuPortal` / `ContextMenuPositioner` / `ContextMenuPopup` parts at any level, there are no separate sub-prefixed components. ```tsx 'use client'; import { ChevronRight } from '@primeicons/react/chevron-right'; import { Copy } from '@primeicons/react/copy'; import { Download } from '@primeicons/react/download'; import { ExternalLink } from '@primeicons/react/external-link'; import { File } from '@primeicons/react/file'; import { Folder } from '@primeicons/react/folder'; import { Link } from '@primeicons/react/link'; import { Send } from '@primeicons/react/send'; import { Trash } from '@primeicons/react/trash'; import { ContextMenu, ContextMenuGroup, ContextMenuSubmenuIndicator, ContextMenuItem, ContextMenuList, ContextMenuPopup, ContextMenuPortal, ContextMenuPositioner, ContextMenuSeparator, ContextMenuSubmenu, ContextMenuSubmenuTrigger, ContextMenuTrigger } from '@/components/ui/contextmenu'; export default function SubmenuDemo() { return ( Right-click here Copy Share Send via email Copy link Open in new tab Save as PDF Image PNG JPG WebP SVG ZIP archive Delete ); } ``` ## Related ### Sub-components See [Primitive API](/docs/primitive/components/contextmenu#api) for `ContextMenuRoot`, `ContextMenuTrigger`, `ContextMenuPortal`, `ContextMenuPositioner`, `ContextMenuPopup`, `ContextMenuArrow`, `ContextMenuList`, `ContextMenuLabel`, `ContextMenuSeparator`, `ContextMenuItem`, `ContextMenuRadioGroup`, `ContextMenuSubmenu`, `ContextMenuSubmenuTrigger`, `ContextMenuIndicator` props. ### Hooks See [Headless API](/docs/headless/components/contextmenu#api) for `useContextMenu` and `useContextMenuSubmenu` hook documentation. ### Accessibility ### Screen Reader `ContextMenu` follows the WAI-ARIA Menu pattern. The trigger is a generic content area (rendered as `div` by default) with `aria-haspopup="menu"` and `tabIndex={0}` so keyboard users can focus it. The popup uses `role="menu"`; items use `menuitem`, `menuitemcheckbox`, or `menuitemradio` based on their type. ### Keyboard Support | Key | Function | | ------------------------------- | -------------------------------------------------------------- | | `Shift+F10` / `ContextMenu` key | Opens the menu when the trigger has focus. | | `down arrow` | Moves focus to the next item. | | `up arrow` | Moves focus to the previous item. | | `home` | Moves focus to the first item. | | `end` | Moves focus to the last item. | | `enter` / `space` | Activates the focused item. | | `right arrow` (on a subtrigger) | Opens the submenu and focuses its first item. | | `left arrow` (inside a submenu) | Closes the submenu and returns focus to the parent subtrigger. | | `escape` | Closes the menu and returns focus to the trigger. | | Any printable character | Jumps to the next item whose text starts with the character. | # DataTable DataTable displays tabular data with sorting, pagination, selection and filtering features. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Database } from '@primeicons/react/database'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function Preview() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); return (
Product Category Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/datatable.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { DataTable, DataTableCell, DataTableHeader, DataTableFooter, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableSortOrder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTable, DataTableTableContainer, DataTablePagination } from '@/components/ui/datatable'; ``` ```tsx Column {({ item }) => ( {item.name} )} ``` ## Examples ### Basic Displays a list of items in tabular format. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function BasicDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); return (
Product Category Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
)}
); } ``` ### Striped Rows Use the `stripedRows` prop to render rows with alternating background colors. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function ProductTable({ stripedRows, showGridlines, size }: { stripedRows?: boolean; showGridlines?: boolean; size?: 'small' | 'large' }) { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 5)); }, []); return ( Name Category Price Status {({ item }: { item: Product }) => ( {item.name} {item.category} ${item.price} {statusLabel[item.inventoryStatus]} )} ); } export default function StripedDemo() { return (
Striped Rows
Gridlines
Small + Striped + Gridlines
Large
); } ``` ### Selection Row selection is driven by the `selectionMode` prop (`single` or `multiple`). The `selectionKeys` prop holds a map of selected row keys and `onSelectionChange` fires when the selection changes. #### Single One row at a time. Clicking a different row replaces the previous selection. ```tsx 'use client'; import { Database } from '@primeicons/react/database'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableSelectionEvent } from 'primereact/datatable'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function SingleSelectionDemo() { const [products, setProducts] = React.useState([]); const [selectionKeys, setSelectionKeys] = React.useState>({}); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); return (
setSelectionKeys(e.value)} > Product Category Qty Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` #### Multiple Multiple rows without a dedicated column. Pair with `metaKeySelection` so Ctrl/Cmd + Click toggles rows and Shift + Click selects a range. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { ShoppingCart } from '@primeicons/react/shopping-cart'; import { Database } from '@primeicons/react/database'; import { Trash } from '@primeicons/react/trash'; import type { DataTableSelectionEvent } from 'primereact/datatable'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function MultipleSelectionDemo() { const [products, setProducts] = React.useState([]); const [selectionKeys, setSelectionKeys] = React.useState>({}); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); const selectedIds = React.useMemo(() => Object.keys(selectionKeys).filter((k) => selectionKeys[k]), [selectionKeys]); const totalValue = React.useMemo( () => products.filter((p) => selectedIds.includes(p.id)).reduce((sum, p) => sum + p.price, 0), [products, selectedIds] ); return (
Selected {selectedIds.length} {selectedIds.length > 0 && ( Total ${totalValue} )}

Click to replace the selection. Ctrl/Cmd + Click to toggle a row, Shift + Click to select a range.

setSelectionKeys(e.value)} > Product Category Qty Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` #### Checkbox Checkbox-based multiple selection with a header select-all. ```tsx 'use client'; import { Checkbox } from '@/components/ui/checkbox'; import { DataTable, DataTableCell, DataTableHeader, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import { CustomerService, type Customer } from '@/shared/services/customer.service'; import { DataTable as PRDataTable, type DataTableSelectionEvent, type DataTableSelectionInstance } from '@primereact/ui/datatable'; import type { CheckboxRootChangeEvent } from 'primereact/checkbox'; import * as React from 'react'; const statusSeverity: Record = { qualified: 'success', unqualified: 'danger', negotiation: 'warn', new: 'info', renewal: 'secondary', proposal: 'info' }; export default function CheckboxSelectionDemo() { const [customers, setCustomers] = React.useState([]); const [selectionKeys, setSelectionKeys] = React.useState>({}); React.useEffect(() => { setCustomers(CustomerService.getData().slice(0, 8)); }, []); const selectedCount = Object.values(selectionKeys).filter(Boolean).length; return (
setSelectionKeys(e.value)} >
Customers {selectedCount > 0 ? `${selectedCount} selected` : `${customers.length} total`}
{({ isAllSelected, isSomeSelected, toggleAll }: DataTableSelectionInstance) => ( toggleAll(e.originalEvent)} /> )} Name Country Status Balance {({ item }: { item: Customer }) => ( {({ isSelected, toggle }: DataTableSelectionInstance) => ( toggle(e.originalEvent)} /> )} {item.name} {item.country.name} {item.status} ${item.balance.toLocaleString()} )}
); } ``` #### Keyboard Arrow Up/Down moves focus between rows, Space or Enter toggles the focused row, and Shift + Arrow extends a range. ```tsx 'use client'; import { Database } from '@primeicons/react/database'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableSelectionEvent } from 'primereact/datatable'; import { Badge } from '@/components/ui/badge'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function KeyboardDemo() { const [products, setProducts] = React.useState([]); const [selectionKeys, setSelectionKeys] = React.useState>({}); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); const selectedCount = Object.values(selectionKeys).filter(Boolean).length; return (
↑{' '} ↓ navigate,{' '} Space /{' '} Enter select,{' '} Shift + ↑↓ range
Selected {selectedCount}
setSelectionKeys(e.value)} > Product Category Qty Price Status {({ item }: { item: Product }) => (
{item.name} {item.name}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Sort Wrap a column header with `DataTableSort field="…"` to make it sortable. `DataTableSortIndicator match="asc | desc | unsorted"` renders direction icons and `DataTableSortOrder` shows the priority badge in multi-sort mode. #### Single Clicking a column header cycles through ascending, descending and unsorted. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableSortOrder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Badge } from '@/components/ui/badge'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Database } from '@primeicons/react/database'; import { SortAlt } from '@primeicons/react/sort-alt'; import { SortAmountDown } from '@primeicons/react/sort-amount-down'; import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt'; import type { DataTableSortOrderInstance } from 'primereact/datatable'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) { return ( {children} {({ index }: DataTableSortOrderInstance) => ( {index} )} ); } export default function SortDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 10)); }, []); return (
Product Category Quantity Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` #### Multiple Hold Ctrl/Cmd and click multiple column headers to sort by several fields at once. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableSortOrderInstance } from 'primereact/datatable'; import { SortAlt } from '@primeicons/react/sort-alt'; import { Database } from '@primeicons/react/database'; import { SortAmountDown } from '@primeicons/react/sort-amount-down'; import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt'; import { Badge } from '@/components/ui/badge'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableSortOrder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) { return ( {children} {({ index }: DataTableSortOrderInstance) => ( {index} )} ); } export default function MultiSortDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 10)); }, []); return (
Product Category Quantity Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` #### Presort Apply an initial sort on mount via `defaultSortField` + `defaultSortOrder`. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableSortOrderInstance } from 'primereact/datatable'; import { SortAlt } from '@primeicons/react/sort-alt'; import { Database } from '@primeicons/react/database'; import { SortAmountDown } from '@primeicons/react/sort-amount-down'; import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt'; import { Badge } from '@/components/ui/badge'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableSortOrder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) { return ( {children} {({ index }: DataTableSortOrderInstance) => ( {index} )} ); } export default function PresortDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 10)); }, []); return (
Product Category Quantity Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` For multi-column presort, use `defaultMultiSortMeta`. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableSortOrderInstance } from 'primereact/datatable'; import { SortAlt } from '@primeicons/react/sort-alt'; import { Database } from '@primeicons/react/database'; import { SortAmountDown } from '@primeicons/react/sort-amount-down'; import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt'; import { Badge } from '@/components/ui/badge'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableSortOrder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { OverlayBadge } from '@/components/ui/overlaybadge'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) { return ( {children} {({ index }: DataTableSortOrderInstance) => ( {index} )} ); } export default function PresortMultiDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 10)); }, []); return (
Product Category Quantity Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Pagination DataTable exposes pagination state via render prop, allowing complete control over the pagination UI. ```tsx 'use client'; import { DataTable, DataTableCell, DataTablePagination, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Paginator, PaginatorFirst, PaginatorLast, PaginatorNext, PaginatorPages, PaginatorPrev } from '@/components/ui/paginator'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTablePaginationInstance } from 'primereact/datatable'; import type { PaginatorRootChangeEvent } from 'primereact/paginator'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function PaginationDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData()); }, []); return (
Name Category Qty Status {({ item }: { item: Product }) => ( {item.name} {item.category} {item.quantity} {statusLabel[item.inventoryStatus]} )} {({ page, rows, totalRecords, onPageChange }: DataTablePaginationInstance) => ( { if (e.originalEvent) onPageChange(e.originalEvent, e.value - 1); }} > )}
); } ``` ### Scroll Set `scrollable` on `DataTable` to enable scrolling. #### Horizontal When the combined column widths exceed the container, the table scrolls horizontally. ```tsx 'use client'; import { CustomerService, type Customer } from '@/shared/services/customer.service'; import { Database } from '@primeicons/react/database'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { qualified: 'success', unqualified: 'danger', negotiation: 'warn', new: 'info', renewal: 'secondary', proposal: 'info' }; export default function ScrollHorizontalDemo() { const [customers, setCustomers] = React.useState([]); React.useEffect(() => { setCustomers(CustomerService.getData()); }, []); return (
Id Name Country Date Balance Company Status Activity Representative {({ item }: { item: Customer }) => ( {item.id} {item.name} {item.country.name} {item.date} ${Number(item.balance).toLocaleString()} {item.company} {item.status} {item.activity}% {item.representative.name} )}

Fetching customers

Hang tight, your data will appear here in a moment.

); } ``` #### Frozen Columns Pin multiple columns to either side using `frozen` and `frozenAlignment`. ```tsx 'use client'; import { CustomerService, type Customer } from '@/shared/services/customer.service'; import { Database } from '@primeicons/react/database'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { qualified: 'success', unqualified: 'danger', negotiation: 'warn', new: 'info', renewal: 'secondary', proposal: 'info' }; export default function ScrollFrozenColumnsMultiDemo() { const [customers, setCustomers] = React.useState([]); React.useEffect(() => { setCustomers(CustomerService.getData()); }, []); return (
Id Name Country Company Representative Date Activity Status Balance {({ item }: { item: Customer }) => ( #{String(item.id)} {String(item.name)}
{CustomerService.getFlag(item.country.code)} {item.country.name}
{String(item.company)} {String(item.representative.name)} {String(item.date)} {String(item.activity)}% {String(item.status)} ${Number(item.balance).toLocaleString()}
)}

Fetching customers

Hang tight, your data will appear here in a moment.

); } ``` #### Frozen Rows Render a `DataTableFrozenTBody` above the regular `DataTableTBody` to keep specific rows pinned to the top. ```tsx 'use client'; import { CustomerService, type Customer } from '@/shared/services/customer.service'; import { Lock } from '@primeicons/react/lock'; import { Database } from '@primeicons/react/database'; import { LockOpen } from '@primeicons/react/lock-open'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableFrozenTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { qualified: 'success', unqualified: 'danger', negotiation: 'warn', new: 'info', renewal: 'secondary', proposal: 'info' }; export default function ScrollFrozenRowsDemo() { const [customers, setCustomers] = React.useState([]); const [lockedIds, setLockedIds] = React.useState([]); React.useEffect(() => { const data = CustomerService.getData(); setCustomers(data); setLockedIds([data[0].id]); }, []); const { lockedCustomers, unlockedCustomers } = React.useMemo(() => { const locked: Customer[] = []; const unlocked: Customer[] = []; for (const c of customers) { if (lockedIds.includes(c.id)) locked.push(c); else unlocked.push(c); } return { lockedCustomers: locked, unlockedCustomers: unlocked }; }, [customers, lockedIds]); const toggleLock = React.useCallback((id: number) => { setLockedIds((prev) => { if (prev.includes(id)) return prev.filter((x) => x !== id); if (prev.length >= 2) return prev; return [...prev, id]; }); }, []); const canLockMore = lockedIds.length < 2; // Refs so renderRow and tbodyRender keep stable identities across toggles — // avoids invalidating TBody's render-prop and FrozenTBody children on every // lock click. The refs are read at render-time, so each row still gets the // current canLockMore value. const canLockMoreRef = React.useRef(canLockMore); canLockMoreRef.current = canLockMore; const toggleLockRef = React.useRef(toggleLock); toggleLockRef.current = toggleLock; const renderRow = React.useCallback((item: Customer, { frozen = false }: { frozen?: boolean } = {}) => { const canLock = canLockMoreRef.current; const onClick = () => toggleLockRef.current(item.id); return ( {item.name}
{CustomerService.getFlag(item.country.code)} {item.country.name}
{item.representative.name} {item.status}
); }, []); const tbodyRender = React.useCallback(({ item }: { item: Customer }) => renderRow(item as Customer), [renderRow]); return (
Name Country Representative Status {lockedCustomers.map((item) => renderRow(item, { frozen: true }))} {tbodyRender}

Fetching customers

Hang tight, your data will appear here in a moment.

); } ``` ### Row Expansion Expand rows to show additional detail content. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { ChevronDown } from '@primeicons/react/chevron-down'; import { Database } from '@primeicons/react/database'; import { ChevronRight } from '@primeicons/react/chevron-right'; import { Star } from '@primeicons/react/star'; import { StarFill } from '@primeicons/react/star-fill'; import type { DataTableExpansionEvent } from 'primereact/datatable'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableRowExpansion, DataTableRowToggle, DataTableRowToggleIndicator, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Rating, RatingOff, RatingOn, RatingOption } from '@/components/ui/rating'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; function ProductRating({ value }: { value: number }) { return ( {Array.from({ length: 5 }).map((_, i) => ( ))} ); } function productImageUrl(image: string): string { return `https://primefaces.org/cdn/primevue/images/product/${image}`; } export default function ExpansionDemo() { const [products, setProducts] = React.useState([]); const [expandedKeys, setExpandedKeys] = React.useState>({}); React.useEffect(() => { const data = ProductService.getProductsData().slice(0, 6); setProducts(data); // Warm the browser cache so the first row-expansion open isn't blocked // on a cold image fetch. The expansion panel itself only mounts when a // row is open, so without this prefetch the user waits for the network // round-trip the first time they click the toggle. for (const product of data) { const img = new window.Image(); img.src = productImageUrl(product.image); } }, []); return (
setExpandedKeys(e.value)} > Product Category Price Status {({ item }: { item: Product }) => ( {item.name} {item.category} ${item.price} {statusLabel[item.inventoryStatus]}
{item.name}

{item.name}

SKU: {item.code}
({item.rating}/5)
Category
{item.category}
In stock
{item.quantity} units
Price
${item.price}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Column Resize Enable column resizing with drag handles via the `resizableColumns` prop. #### Fit Mode Dragging takes width from the adjacent column; total table width stays the same. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Database } from '@primeicons/react/database'; import { DataTable, DataTableCell, DataTableColumnResizeIndicator, DataTableColumnResizer, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; export default function ResizeDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 6)); }, []); return (
Product Category Qty Price {({ item }: { item: Product }) => (
{item.name} {item.name}
{item.category} {item.quantity} ${item.price}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` #### Expand Mode Dragging grows or shrinks the whole table; adjacent columns keep their widths. ```tsx 'use client'; import { Database } from '@primeicons/react/database'; import { ProductService, type Product } from '@/shared/services/product.service'; import { DataTable, DataTableCell, DataTableColumnResizeIndicator, DataTableColumnResizer, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function ResizeExpandDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 6)); }, []); return (
Product Category Qty Price Status {({ item }: { item: Product }) => (
{item.name} {item.name}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Column Reorder Drag and drop column headers to reorder columns. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import type { DataTableColumnReorderEvent } from 'primereact/datatable'; import { ArrowDown } from '@primeicons/react/arrow-down'; import { ArrowUp } from '@primeicons/react/arrow-up'; import { Bars } from '@primeicons/react/bars'; import { Database } from '@primeicons/react/database'; import { DataTable, DataTableCell, DataTableColumnReorder, DataTableColumnReorderIndicatorDown, DataTableColumnReorderIndicatorUp, DataTableColumnReorderTarget, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; interface Column { field: string; header: string; } const initialColumns: Column[] = [ { field: 'name', header: 'Product' }, { field: 'category', header: 'Category' }, { field: 'quantity', header: 'Quantity' }, { field: 'price', header: 'Price' }, { field: 'inventoryStatus', header: 'Status' } ]; function renderCell(field: string, item: Product) { switch (field) { case 'name': return (
{item.name} {item.name}
); case 'category': return {item.category}; case 'quantity': return item.quantity; case 'price': return ${item.price}; case 'inventoryStatus': return {statusLabel[item.inventoryStatus]}; default: return null; } } export default function ReorderDemo() { const [products, setProducts] = React.useState([]); const [columns, setColumns] = React.useState(initialColumns); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 6)); }, []); return (
{ const reordered = [...columns]; const [moved] = reordered.splice(e.dragIndex, 1); reordered.splice(e.dropIndex, 0, moved); setColumns(reordered); }} > {columns.map((col) => ( {col.header} ))} c.field).join(',')}> {({ item }: { item: Product }) => ( {columns.map((col) => ( {renderCell(col.field, item)} ))} )}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Row Reorder Drag and drop rows to reorder data. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Bars } from '@primeicons/react/bars'; import { Database } from '@primeicons/react/database'; import type { DataTableRowReorderEvent } from 'primereact/datatable'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableRowReorder, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function RowReorderDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 6)); }, []); return (
setProducts(e.value as unknown as Product[])} > Product Category Price Status {({ item, index }: { item: Product; index: number }) => (
{item.name}
{item.name} {item.code}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Column Group Multi-level headers with rowspan and colspan. ```tsx 'use client'; import { ArrowDown } from '@primeicons/react/arrow-down'; import { Database } from '@primeicons/react/database'; import { ArrowUp } from '@primeicons/react/arrow-up'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTFoot, DataTableTFootCell, DataTableTFootRow, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; interface Sale { id: string; product: string; lastYearSale: number; thisYearSale: number; lastYearProfit: number; thisYearProfit: number; } const sales: Sale[] = [ { id: '1', product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342 }, { id: '2', product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122 }, { id: '3', product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500 }, { id: '4', product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 650323 }, { id: '5', product: 'Bracelet', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332 } ]; function Trend({ current, previous }: { current: number; previous: number }) { const up = current >= previous; return (
{current}% {up ? : }
); } export default function ColumnGroupDemo() { const totals = React.useMemo(() => { return sales.reduce( (acc, s) => ({ lastYear: acc.lastYear + s.lastYearProfit, thisYear: acc.thisYear + s.thisYearProfit }), { lastYear: 0, thisYear: 0 } ); }, []); return (
Product Sale Rate Profits Last Year This Year Last Year This Year {({ item }: { item: Sale }) => ( {item.product} {item.lastYearSale}% ${item.lastYearProfit.toLocaleString()} = item.lastYearProfit ? 'success' : 'danger'}> ${item.thisYearProfit.toLocaleString()} )}

Fetching sales

Hang tight, the latest figures will appear here in a moment.

Totals ${totals.lastYear.toLocaleString()} ${totals.thisYear.toLocaleString()}
); } ``` Sort and filter work on any leaf header cell in a grouped layout. ```tsx 'use client'; import { ArrowDown } from '@primeicons/react/arrow-down'; import { Database } from '@primeicons/react/database'; import { ArrowUp } from '@primeicons/react/arrow-up'; import { SortAlt } from '@primeicons/react/sort-alt'; import { SortAmountDown } from '@primeicons/react/sort-amount-down'; import { SortAmountUpAlt } from '@primeicons/react/sort-amount-up-alt'; import { FilterMatchMode } from 'primereact/datatable'; import type { DataTableFilterInstance } from 'primereact/datatable'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableFilter, DataTableRow, DataTableSort, DataTableSortIndicator, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { InputText } from '@/components/ui/inputtext'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; interface Sale { id: string; product: string; lastYearSale: number; thisYearSale: number; lastYearProfit: number; thisYearProfit: number; } const sales: Sale[] = [ { id: '1', product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54_406, thisYearProfit: 43_342 }, { id: '2', product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423_132, thisYearProfit: 312_122 }, { id: '3', product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12_321, thisYearProfit: 8_500 }, { id: '4', product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745_232, thisYearProfit: 650_323 }, { id: '5', product: 'Bracelet', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643_242, thisYearProfit: 500_332 } ]; function Trend({ current, previous }: { current: number; previous: number }) { const up = current >= previous; return (
{current}% {up ? : }
); } function SortableHeader({ field, children }: { field: string; children: React.ReactNode }) { return ( {children} ); } export default function ColumnGroupFilterSortDemo() { const [filters, setFilters] = React.useState>({ product: { value: null, matchMode: FilterMatchMode.Contains }, thisYearSale: { value: null, matchMode: FilterMatchMode.Gte } }); return (
}) => setFilters(e.filters as typeof filters)} showGridlines > Product Sale Rate Profits Last Year This Year Last Year This Year {({ value, onChange }: DataTableFilterInstance) => ( ) => onChange(e, e.target.value)} placeholder="Search" size="small" fluid /> )} {({ value, onChange }: DataTableFilterInstance) => ( ) => onChange(e, e.target.value ? Number(e.target.value) : null, 'gte') } placeholder="≥" size="small" fluid /> )} {({ item }: { item: Sale }) => ( {item.product} {item.lastYearSale}% ${item.lastYearProfit.toLocaleString()} = item.lastYearProfit ? 'success' : 'danger'}> ${item.thisYearProfit.toLocaleString()} )}

Fetching sales

Hang tight, the latest figures will appear here in a moment.

); } ``` ### Filter Filtering is driven by a `filters` object passed to `DataTable`. Global search is opt-in via `globalFilter` + `globalFilterFields`. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableHeader, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { IconField } from '@/components/ui/iconfield'; import { InputText } from '@/components/ui/inputtext'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Search } from '@primeicons/react/search'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function FilterDemo() { const [products, setProducts] = React.useState([]); const [globalFilter, setGlobalFilter] = React.useState(''); React.useEffect(() => { setProducts(ProductService.getProductsData()); }, []); return (
) => setGlobalFilter(e.target.value)} />
Name Category Price Status {({ item }: { item: Product }) => ( {item.name} {item.category} ${item.price} {statusLabel[item.inventoryStatus]} )}
); } ``` ### Header & Footer Use `DataTableHeader` and `DataTableFooter` to add summary information above and below the table. ```tsx 'use client'; import { DataTable, DataTableCell, DataTableFooter, DataTableHeader, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import { ProductService, type Product } from '@/shared/services/product.service'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function TemplateDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 6)); }, []); return (
Products {products.length} items
Product Category Price Status {({ item }: { item: Product }) => (
{item.name} {item.name}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
)}
Total products: {products.length}
); } ``` ### Export Export table data to CSV with customizable fields and headers. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { FileExport } from '@primeicons/react/file-export'; import { Database } from '@primeicons/react/database'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableExport, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function ExportDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { setProducts(ProductService.getProductsData().slice(0, 8)); }, []); return (
Export visible rows to CSV with custom column headers. Export CSV
Product Category Qty Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} {item.quantity} ${item.price} {statusLabel[item.inventoryStatus]}
)}

Fetching products

Hang tight, your data will appear here in a moment.

); } ``` ### Loading #### Overlay Set the `loading` prop on `DataTable` and provide a spinner inside `DataTableLoading`. An absolutely positioned overlay dims the table. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Refresh } from '@primeicons/react/refresh'; import { Spinner } from '@primeicons/react/spinner'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableLoading, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; export default function LoadingOverlayDemo() { const [products, setProducts] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); const refresh = React.useCallback(() => { setIsLoading(true); setTimeout(() => { setProducts(ProductService.getProductsData().slice(0, 6)); setIsLoading(false); }, 1500); }, []); React.useEffect(() => { refresh(); }, [refresh]); return (
Click refresh to simulate a network fetch.
Product Category Price Status {({ item }: { item: Product }) => (
{item.name}
{item.name} {item.code}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
)}
Loading products…
); } ``` #### Skeleton Render placeholder rows filled with `Skeleton` elements while the request is in flight. ```tsx 'use client'; import { ProductService, type Product } from '@/shared/services/product.service'; import { Refresh } from '@primeicons/react/refresh'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; import { Skeleton } from '@/components/ui/skeleton'; import { Tag } from '@/components/ui/tag'; import * as React from 'react'; const statusSeverity: Record = { INSTOCK: 'success', LOWSTOCK: 'warn', OUTOFSTOCK: 'danger' }; const statusLabel: Record = { INSTOCK: 'In Stock', LOWSTOCK: 'Low Stock', OUTOFSTOCK: 'Out of Stock' }; const placeholders = Array.from({ length: 6 }, (_, i) => ({ id: `placeholder-${i}` })); export default function LoadingSkeletonDemo() { const [products, setProducts] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); const refresh = React.useCallback(() => { setIsLoading(true); setProducts([]); setTimeout(() => { setProducts(ProductService.getProductsData().slice(0, 6)); setIsLoading(false); }, 1500); }, []); React.useEffect(() => { refresh(); }, [refresh]); const rows = isLoading ? placeholders : products; return (
Click refresh to simulate a network fetch.
Product Category Price Status {({ item }: { item: Product }) => isLoading ? (
) : (
{item.name}
{item.name} {item.code}
{item.category} ${item.price} {statusLabel[item.inventoryStatus]}
) }
); } ``` ### Empty State Custom empty state when no data is available, using `EmptyTBody`. ```tsx 'use client'; import { Inbox } from '@primeicons/react/inbox'; import { Plus } from '@primeicons/react/plus'; import { Button } from '@/components/ui/button'; import { DataTable, DataTableCell, DataTableEmptyTBody, DataTableRow, DataTableTBody, DataTableTHead, DataTableTHeadCell, DataTableTHeadRow, DataTableTHeadTitle, DataTableTable, DataTableTableContainer } from '@/components/ui/datatable'; export default function EmptyDemo() { return (
Product Category Price Status {({ item }: { item: Record }) => ( {String(item.name)} {String(item.category)} ${String(item.price)} {String(item.status)} )}

No products yet

Add your first product to see it listed here.

); } ``` ## Related ### Sub-Components See [DataTable Primitive](/docs/primitive/components/datatable#api) for the full sub-component API. ### Hooks See [useDataTable](/docs/headless/components/datatable#api) for the headless hook API. ### Accessibility See [DataTable Primitive](/docs/primitive/components/datatable#accessibility) for WAI-ARIA compliance details and keyboard support. # DataView DataView displays data in grid or list layout with pagination and sorting features. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { DataView, DataViewContent } from '@/components/ui/dataview'; import { Tag } from '@/components/ui/tag'; import { ProductService } from '@/shared/services/product.service'; import { Heart, ShoppingCart, StarFill } from '@primeicons/react'; import * as React from 'react'; interface Product { id: string; code: string; name: string; description: string; image: string; price: number; category: string; quantity: number; inventoryStatus: string; rating: number; } export default function Preview() { const [products, setProducts] = React.useState([]); React.useEffect(() => { ProductService.getProductsSmall().then((data) => setProducts(data.slice(0, 5))); }, []); const getSeverity = (product: Product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return undefined; } }; return (
{products.map((product, index) => (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
))}
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/dataview.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { DataView, DataViewContent, DataViewHeader, DataViewFooter, DataViewEmpty } from '@/components/ui/dataview'; ``` ```tsx ``` ## Examples ### Basic Displays a list of items with support for layout switching. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { DataView, DataViewContent } from '@/components/ui/dataview'; import { Tag } from '@/components/ui/tag'; import { ProductService } from '@/shared/services/product.service'; import { Heart, ShoppingCart, StarFill } from '@primeicons/react'; import * as React from 'react'; interface Product { id: string; code: string; name: string; description: string; image: string; price: number; category: string; quantity: number; inventoryStatus: string; rating: number; } export default function BasicDemo() { const [products, setProducts] = React.useState([]); React.useEffect(() => { ProductService.getProductsSmall().then((data) => setProducts(data.slice(0, 5))); }, []); const getSeverity = (product: Product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return undefined; } }; return (
{products.map((product, index) => (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
))}
); } ``` ### Pagination Use `Paginator` with `DataViewFooter` to navigate large datasets efficiently. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { DataView, DataViewContent, DataViewFooter } from '@/components/ui/dataview'; import { Paginator, PaginatorFirst, PaginatorLast, PaginatorNext, PaginatorPages, PaginatorPrev } from '@/components/ui/paginator'; import { Tag } from '@/components/ui/tag'; import { ProductService } from '@/shared/services/product.service'; import { Heart, ShoppingCart, StarFill } from '@primeicons/react'; import { PaginatorRootChangeEvent } from 'primereact/paginator'; import * as React from 'react'; interface Product { id: string; code: string; name: string; description: string; image: string; price: number; category: string; quantity: number; inventoryStatus: string; rating: number; } export default function PaginationDemo() { const [products, setProducts] = React.useState([]); const [page, setPage] = React.useState(1); const itemsPerPage = 5; React.useEffect(() => { ProductService.getProductsSmall().then((data) => setProducts(data)); }, []); const getSeverity = (product: Product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return undefined; } }; return (
{products.slice((page - 1) * itemsPerPage, page * itemsPerPage).map((product, index) => (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
))}
setPage(e.value)} className="border-t border-surface-200 dark:border-surface-700 pt-4" >
); } ``` ### Sorting Use `useDataView` to sort items by a specific field and order. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { DataView, DataViewContent, DataViewHeader } from '@/components/ui/dataview'; import { Tag } from '@/components/ui/tag'; import { ToggleButton } from '@/components/ui/togglebutton'; import { ToggleButtonGroup } from '@/components/ui/togglebuttongroup'; import { ProductService } from '@/shared/services/product.service'; import { Heart, ShoppingCart, StarFill } from '@primeicons/react'; import { ToggleButtonGroupValueChangeEvent } from 'primereact/togglebuttongroup'; import * as React from 'react'; interface Product { id: string; code: string; name: string; description: string; image: string; price: number; category: string; quantity: number; inventoryStatus: string; rating: number; } export default function SortDemo() { const [products, setProducts] = React.useState([]); const [value, setValue] = React.useState(null); React.useEffect(() => { ProductService.getProductsSmall().then((data) => setProducts(data.slice(0, 5))); }, []); const getSeverity = (product: Product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return undefined; } }; const onSortChange = (value: string | null) => { const sortOrder = value === 'hightolow' ? -1 : value === 'lowtohigh' ? 1 : 0; setValue(value); setProducts((prev) => [...prev].sort((a, b) => (a.price - b.price) * sortOrder)); }; return (
onSortChange(e.value as string)} allowEmpty={false} > Price High to Low Price Low to High
{products.map((product, index) => (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
))}
); } ``` ### Layout Switch between grid and list layouts to display the same data with different visual density. ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { DataView, DataViewContent, DataViewHeader } from '@/components/ui/dataview'; import { Tag } from '@/components/ui/tag'; import { ToggleButton } from '@/components/ui/togglebutton'; import { ToggleButtonGroup } from '@/components/ui/togglebuttongroup'; import { ProductService } from '@/shared/services/product.service'; import { Bars, Heart, ShoppingCart, StarFill, Table } from '@primeicons/react'; import { ToggleButtonGroupValueChangeEvent } from 'primereact/togglebuttongroup'; import * as React from 'react'; interface Product { id: string; code: string; name: string; description: string; image: string; price: number; category: string; quantity: number; inventoryStatus: string; rating: number; } export default function LayoutDemo() { const [products, setProducts] = React.useState([]); const [value, setValue] = React.useState('grid'); React.useEffect(() => { ProductService.getProductsSmall().then((data) => setProducts(data.slice(0, 12))); }, []); const getSeverity = (product: Product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return undefined; } }; const listLayout = () => { return (
{products.map((product, index) => (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
))}
); }; const gridLayout = () => { return (
{products.map((product, index) => { return (
{product.name} {product.inventoryStatus}
{product.category}
{product.name}
{product.rating}
${product.price}
); })}
); }; const list = listLayout(); const grid = gridLayout(); return (
setValue(e.value as string)} allowEmpty={false} > {value === 'list' ? list : grid} ); } ``` ### Loading Use a skeleton layout to represent loading state while data is being fetched. ```tsx 'use client'; import { DataView, DataViewContent, DataViewHeader } from '@/components/ui/dataview'; import { Skeleton } from '@/components/ui/skeleton'; import { ToggleButton } from '@/components/ui/togglebutton'; import { ToggleButtonGroup } from '@/components/ui/togglebuttongroup'; import { Bars, Table } from '@primeicons/react'; import { ToggleButtonGroupValueChangeEvent } from 'primereact/togglebuttongroup'; import * as React from 'react'; export default function LoadingDemo() { const [value, setValue] = React.useState('grid'); const listLayout = () => { return (
{Array.from({ length: 6 }, (_, i) => (
))}
); }; const gridLayout = () => { return (
{Array.from({ length: 6 }, (_, i) => (
))}
); }; const list = listLayout(); const grid = gridLayout(); return (
setValue(e.value as string)} allowEmpty={false} >
{value === 'list' ? list : grid} ); } ``` ## Related ### Sub-Components See [DataView Primitive](/docs/primitive/components/dataview#api) for the full sub-component API. ### Hooks See [useDataView](/docs/headless/components/dataview#api) for the headless hook API. ### Accessibility See [DataView Primitive](/docs/primitive/components/dataview#accessibility) for WAI-ARIA compliance details and keyboard support. # DatePicker DatePicker is a form component to work with dates. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import { Label } from '@/components/ui/label'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; const MS_PER_DAY = 1000 * 60 * 60 * 24; export default function Preview() { const [date, setDate] = React.useState(null); const [start, end] = Array.isArray(date) ? date : [null, null]; const nights = start instanceof Date && end instanceof Date ? Math.round((end.getTime() - start.getTime()) / MS_PER_DAY) : null; return (
setDate(e.value)} > {nights ? `${nights} ${nights === 1 ? 'night' : 'nights'} · Free cancellation up to 24h before check-in` : 'Pick your departure and return dates · Free cancellation'}
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/datepicker.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { DatePicker, DatePickerBody, DatePickerCalendar, DatePickerInput, DatePickerPanel, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; ``` ```tsx setDate(e.value)}> ``` ## Examples ### Basic A simple date selector. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function BasicDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Range Use `selectionMode="range"` to select a start and end date. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function RangeDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)} >
); } ``` ### Multiple Use `selectionMode="multiple"` to select multiple dates independently. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function MultipleDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)} >
); } ``` ### Format Customize the input format with `dateFormat`. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function FormatDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Min / Max Restrict the selectable range with `minDate` and `maxDate`. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function MinMaxDemo() { const [date, setDate] = React.useState(null); const today = new Date(); const minDate = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); const maxDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate()); return (
setDate(e.value)}>
); } ``` ### Month Picker Use `view="month"` to select only month and year. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function MonthPickerDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Year Picker Use `view="year"` to select only the year. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function YearPickerDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Time Use `showTime` to render a time picker beneath the calendar; pass `hourFormat="12"` for AM/PM. ```tsx 'use client'; import { DatePicker, DatePickerBody, DatePickerCalendar, DatePickerInput, DatePickerPanel, DatePickerPopup, DatePickerPortal, DatePickerPositioner, DatePickerTime } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function TimeDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Inline Skip the popup chain and embed the calendar directly. ```tsx 'use client'; import { DatePicker, DatePickerBody, DatePickerCalendar, DatePickerPanel } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function InlineDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Multiple Months Render two side-by-side calendars with `numberOfMonths={2}` and one `DatePickerCalendar` per offset. ```tsx 'use client'; import { DatePicker, DatePickerBody, DatePickerCalendar, DatePickerPanel } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function MultipleMonthsDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Show Week Numbers Add `showWeek` to display ISO week numbers on the leftmost column. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function ShowWeekDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Sizes Use `size` to render small or large variants. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function SizesDemo() { const [date1, setDate1] = React.useState(null); const [date2, setDate2] = React.useState(null); const [date3, setDate3] = React.useState(null); return (
setDate1(e.value)}> setDate2(e.value)}> setDate3(e.value)}>
); } ``` ### Fluid Use `fluid` to make the input span the parent container width. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function FluidDemo() { const [date, setDate] = React.useState(null); return ( setDate(e.value)}> ); } ``` ### Filled Use `variant="filled"` for a filled input style. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function FilledDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ### Disabled Use `disabled` to disable the input. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; export default function DisabledDemo() { return (
); } ``` ### Invalid Use `invalid` to display the input in an error state. ```tsx 'use client'; import { DatePicker, DatePickerCalendar, DatePickerBody, DatePickerPanel, DatePickerInput, DatePickerPopup, DatePickerPortal, DatePickerPositioner } from '@/components/ui/datepicker'; import type { DatePickerRootProps, DatePickerRootValueChangeEvent } from 'primereact/datepicker'; import * as React from 'react'; export default function InvalidDemo() { const [date, setDate] = React.useState(null); return (
setDate(e.value)}>
); } ``` ## Accessibility ### Screen Reader DatePicker uses `combobox` role on the input and `dialog` role on the popup. Day cells use `gridcell` role with `aria-selected` reflecting selection and range state. ### Keyboard Support | Key | Function | | --------------- | ------------------------------------- | | `tab` | Moves focus through interactive parts | | `space / enter` | Selects the focused date | | `arrow keys` | Moves focus by day or grid cell | | `page up/down` | Moves focus by month | | `home / end` | Moves focus to start/end of week | | `escape` | Closes the popup | # Dialog Dialog is a container to display content in an overlay window. ```tsx 'use client'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { Dialog, DialogBackdrop, DialogClose, DialogContent, DialogHeader, DialogHeaderActions, DialogPopup, DialogPortal, DialogPositioner, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { InputText } from '@/components/ui/inputtext'; import { Label } from '@/components/ui/label'; import { Times } from '@primeicons/react'; export default function Preview() { return (
Edit Profile Edit Profile
AM

Amanda Miller

amanda@example.com

Cancel Save Changes
); } ``` ## Installation Shadcn CLI Manual ```bash npx shadcn@latest add https://primereact.dev/r/dialog.json ``` Copy and paste the following code into your project Update the import paths to match your project setup. ## Usage ```tsx import { Dialog, DialogBackdrop, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogHeaderActions, DialogMaximizable, DialogPopup, DialogPortal, DialogPositioner, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; ``` ```tsx ``` ## Examples ### Basic ```tsx 'use client'; import { Button } from '@/components/ui/button'; import { Dialog, DialogBackdrop, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogHeaderActions, DialogPopup, DialogPortal, DialogPositioner, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { InputText } from '@/components/ui/inputtext'; import { Label } from '@/components/ui/label'; import { Times } from '@primeicons/react'; export default function BasicDemo() { return (
Open Trigger Edit Profile
Cancel Save
); } ``` ### Draggable A dialog can be repositioned by dragging its header when the `draggable` prop is enabled. ```tsx 'use client'; import { Times } from '@primeicons/react/times'; import { Button } from '@/components/ui/button'; import { Dialog, DialogBackdrop, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogHeaderActions, DialogPopup, DialogPortal, DialogPositioner, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Textarea } from '@/components/ui/textarea'; export default function DraggableDemo() { return (
Open Note Quick Note

Drag this dialog by its header to reposition it anywhere on the screen.