@construkt-kit/ui
v0.12.0
Published
Shared component library for Construkt Kit frontend apps
Downloads
2,157
Readme
@construkt-kit/ui
60+ UI components for Construkt Kit apps, built on Panda CSS and Ark UI primitives.
Rule: Consumers import components from
@construkt-kit/uiand Panda helpers from their own generatedstyled-system/. UI implementation code imports Panda helpers from#styled-system/*.
Exports
Components
Layout: Box, Flex, Stack, HStack, VStack, Center, Container, Grid, GridItem, Spacer, Float, Separator, Wrap, SimpleGrid
Buttons: Button, IconButton, TooltipIconButton, DeleteButton, EditButton, CloseButton, SelectButton, ButtonGroup
Data Display: DataTable, Badge, Avatar, Image, List, Table, Stat, EmptyState, Card, Carousel, Code, DisplayValue, Kbd
Feedback: Alert, LoadingOverlay, Toaster / toaster, Progress, Skeleton, Spinner
Overlay / Dialog: Dialog, SubmitDialog, DeleteDialog, Drawer, Sheet, Popover, Tooltip, ToggleTip, InfoTip, HoverCard
Form: Field, SubmitForm, Fieldset, Input, Textarea, InputGroup, PasswordInput, SearchInput, NumberInput, Checkbox, CheckboxCard, Switch, Radio / RadioGroup, RadioCard, Slider, TagsInput, Editable, FileUpload, FormFileUpload, PinInput, ColorPicker
Selection / Dropdowns: Select, Listbox, SelectButton, TagSelect, ApplySelect, Combobox — the managed Select/Listbox families take groupBy / groupSort for group headings
Tree: TreeView, useTreeView, TreeSelectList, createTreeCollection, createFileTreeCollection
Date Pickers: Calendar, DatePicker, DatePickerSelect
Navigation & Text: Link, Tabs, Accordion, Breadcrumb, Menu, Text, Heading, Span, SearchHighlight, useHighlight
Misc: ActionBar, Clipboard, Collapsible, Icon, NumberFilter, Pagination, Portal, RatingGroup, ScrollArea, SegmentGroup, Splitter, ThemeToggle, ToggleGroup
Hooks
| Hook | Description |
| --------------------------------------- | --------------------------------------------------------------- |
| useAutoFocus | Auto-focus + optional select on mount |
| useControlledMirror | Mirror a controlled value into local state, ignoring its echoes |
| useDebounceQuery | Immediate + debounced search query state |
| useFileSelect | Returns a file-picker opener; onSelect gets the parsed files |
| useMediaQuery | Subscribe to a CSS media query |
| useBreakpointUp / useBreakpointDown | Whether the viewport is at/above or below a breakpoint |
| useIsMobile | Whether the viewport is below the md breakpoint |
Panda CSS Utilities
ui does not re-export Panda's runtime or types. Apps import css, cx, styled, token and Panda's types from the
styled-system/ their own Panda build generates, through the path their importMap names: Panda only extracts
styles from imports it matches, and the app's token and types know the app's theme extensions:
import { css, cx } from "@construkt-kit/styled-system/css";
import { styled } from "@construkt-kit/styled-system/jsx";
import { token } from "@construkt-kit/styled-system/tokens";
import type { HTMLStyledProps, SystemStyleObject } from "@construkt-kit/styled-system/types";Types
WithRef<T, E> — generic ref forwarding type. PortalledProps — the portalled / portalRef pair on overlay content.
CssProp — the value of a *Css prop, typed with ui's theme. Unstyled<P> — P without style props or *Css
props, plus data-*, for objects of props an app forwards to ui.
Variant Vocabulary
solid > surface > subtle > outline > plain. See ../preset/src/theme/recipes/README.md for full reference.
Component Structure
Each component under src/components/<Name>/ follows this pattern:
index.tsx— public exports (re-exported incomponents/index.tsx)<Name>.tsx— main component implementation<Name>.stories.tsx— Storybook storiestypes.ts— (optional) component-specific types
Compound components (DataTable, Dialog, Menu, etc.) may have subfolders for sub-parts (e.g. DataTable/Header/, DataTable/Body/).
Implementation Patterns
1. Simple styled component (Badge, Input, Text)
One-liner. Use when wrapping a single element with a recipe:
import { ark } from "@ark-ui/react/factory";
import { styled } from "#styled-system/jsx";
import { badge } from "#styled-system/recipes";
export type BadgeProps = ComponentProps<typeof Badge>;
export const Badge = styled(ark.div, badge);For Ark UI field primitives: export const Input = styled(Field.Input, input);
2. Compound component with style context (Dialog, Menu, Tabs)
Use createSlotRecipeContext(recipe) to wrap Ark UI compound parts with slot-based styling:
import { Dialog as ArkDialog } from "@ark-ui/react/dialog";
import { createSlotRecipeContext } from "#styled-system/jsx";
import { dialog } from "#styled-system/recipes";
const { withRootProvider, withContext } = createSlotRecipeContext(dialog);
const Root = withRootProvider(ArkDialog.Root, { defaultProps: lazyOverlayDefaults });
const Content = withContext(ArkDialog.Content, "content"); // "content" = slot name in recipe
const Title = withContext(ArkDialog.Title, "title");
export const Dialog = { Root, Content, Title, ... }; // Export as plain object- Each sub-component is a "slot" in the recipe — Panda generates e.g.
dialog__content,dialog__title - Export as a plain object with capitalized properties →
<Dialog.Root>,<Dialog.Content> - Slot recipes use
sva()(slot variant authority)
3. Compound with custom context (Select, ButtonGroup)
Adds app-level React context on top of the style context pattern:
// ButtonGroup provides variant props to all child Buttons via context
const [ButtonPropsProvider, useButtonPropsContext] = createContext<ButtonVariantProps>({
strict: false,
});
export const ButtonGroup = ({ ref, ...props }) => {
const [variantProps, otherProps] = button.splitVariantProps(props);
return (
<ButtonPropsProvider value={variantProps}>
<Group
ref={ref}
{...otherProps}
/>
</ButtonPropsProvider>
);
};
// Child Button merges inherited props from context
export const Button = ({ ref, ...props }) => {
const propsContext = useButtonPropsContext();
const mergedProps = mergeProps(propsContext, { ref, ...props });
// ...
};Key utilities:
splitVariantProps(props)— separates recipe variant props (variant,size) from other propsmergeProps()— merges context-inherited props with local props (local wins)strict: false— context is optional; component works standalone too
Theme Architecture
@construkt-kit/preset exports construktKitPreset, assembled from:
../preset/src/theme/tokens/— primitive tokens (colors, spacing, fonts, radii, etc.)../preset/src/theme/semantic-tokens/— light/dark mode tokens (colors.ts,shadows.ts)colorPalette(color)generatessolid,surface,subtle,outline,plainsub-tokens- Available palettes:
brand,slate,gray,blue,red,green,orange,yellow neutralis an alias palette (defaults to slate) used for default chrome
../preset/src/theme/recipes/— component recipes (variants, sizes, defaults)../preset/src/theme/conditions.ts— custom Panda CSS conditions../preset/src/theme/keyframes.ts,animation-styles.ts,text-styles.ts,layer-styles.ts
styled-system/ is ui's own Panda runtime
panda codegengenerates it intopackages/ui/styled-system/frompanda.config.ts; source imports it as#styled-system/*through theimportsfield inpackage.json- It ships with the package.
panda libexports it as@construkt-kit/ui/{css,jsx,patterns,recipes,tokens}and writes the design-system manifest topanda/ distimports it instead of bundling it, so an app ondesignSystem: "@construkt-kit/ui"generates astyled-system/that re-exports this runtime; an app on@construkt-kit/pages, a nested design system, generates a full local copy@pandacss/devis the build-time tool — used in@construkt-kit/preset, ui, pages and app-level Panda configs
Consequence for downstream presets: shipped prop types carry construkt's own
recipe variants, frozen at build time. A preset that adds a variant value still
styles correctly — recipe class names are string-interpolated and the app's own
Panda build emits the CSS — but will not typecheck. Extend the system through
colorPalette and textStyle rather than new variant names; a new variant
key is worse still, since it reaches the DOM as a stray attribute and ui's
runtime ignores downstream compoundVariants and defaultVariants.
Style props on ui components go through that same runtime, which knows only the preset's conditions, utilities,
shorthands and breakpoints. An app may override token values, but a condition, utility, shorthand or breakpoint it
adds works only through its own css and styled, never as a prop on a ui component.
A recipe's own properties outrank the values of a textStyle it applies, so
overriding a text style only changes what the recipe leaves unset. Slots that
apply the label text style set no color, so an app's label text style can.
Merging over a ckit recipe works but logs design_system_artifact_conflict on
every Panda run, and only a global logLevel silences it. Reach for a token
first — fonts.heading sets the Heading face, the label text style styles
form labels — and keep recipe merges for real restyles, accepting the warning.
Token Path Syntax
| Context | Syntax | Example |
| --------------------- | ------------------ | ----------------------------- |
| In recipe definitions | Bare token paths | bg: "colorPalette.solid.bg" |
| Token references | Curly braces | {colors.brand.500} |
| In components (JS) | token() function | token('colors.brand.500') |
| In components (CSS) | Short paths | css({ bg: 'brand.500' }) |
Semantic token layers: bg.*, fg.*, border.*, neutral.*, colorPalette.*
Condition Overrides
The theme redefines standard pseudo-selectors:
| Condition | Behavior |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _hover, _active | Also match [data-hover]/[data-active], and skip :disabled and [data-disabled]. _hover applies only where the device can hover and ranks below every other condition, states (_active, _checked, _selected, _focusVisible…) as well as _dark, _odd/_even and the like, so a state keeps its look under the pointer; nest _hover inside the other condition (_dark: { _hover: … }) to style hovering there, or use a semantic token |
| _checked | Matches 5 selectors: :checked, [data-checked], [data-state=checked], [aria-checked=true], [data-state=indeterminate] |
| _light | :where(:root, .light) & — explicit light mode, which _dark overrides |
Dark mode
Every semantic token carries a light and a dark value, so dark mode is a token flip — no per-component work. Activation is a dark class on an ancestor (Panda's _dark: .dark & condition); light is the :root default (no class needed). The neutral chrome (bg/fg/border/neutral.*) uses the Radix "slate" dark ramp (colors.slateDark.1–12) on the dark side while light stays Tailwind slate. html { color-scheme } also flips so native scrollbars, form controls, and autofill match.
The runtime lives in @construkt-kit/ui:
import { ColorModeProvider, useColorMode, ThemeToggle, colorModeScript } from "@construkt-kit/ui";
// 1. Wrap the app. defaultMode: "light" | "dark" | "system" (default "system").
<ColorModeProvider defaultMode="system">
<App />
</ColorModeProvider>;
// 2. (Optional) inline in <head> before hydration to avoid a light-mode flash:
// <script dangerouslySetInnerHTML={{ __html: colorModeScript }} />
// For a non-"system" default, match it: createColorModeScript({ defaultMode: "dark" }).
// 3. Read/set the mode anywhere under the provider:
const { mode, resolvedMode, setMode, toggle } = useColorMode();
// 4. Or drop in the prebuilt button:
<ThemeToggle />;ColorModeProvider toggles the dark class on <html> (the preset flips color-scheme from it), resolves "system" via prefers-color-scheme, and persists the choice to localStorage (construkt-color-mode). In Storybook, use the toolbar theme switcher (@storybook/addon-themes) to preview any story in dark.
recipes vs slotRecipes
Both are registered in ../preset/src/theme/recipes/index.ts:
recipes(simplecva()):badge,button,code,heading,icon,input,kbd,link,skeleton,spinner,text,textarea, etc. — single-element componentsslotRecipes(compoundsva()):accordion,dialog,menu,listbox,tabs, etc. — multi-slot components usingcreateSlotRecipeContext()- Naming exception:
switchRecipekey (notswitch— JS reserved word)
File Map
| Path | Purpose |
| -------------------------------------- | ------------------------------------------------- |
| src/index.ts | Public barrel export |
| src/types.ts | Shared types (WithRef, etc.) |
| src/components/index.tsx | Component barrel export |
| src/components/<Name>/ | Individual component folders |
| src/hooks/ | Shared hooks |
| ../preset/src/theme/recipes/ | All component recipes |
| ../preset/src/theme/tokens/ | Primitive design tokens |
| ../preset/src/theme/semantic-tokens/ | Semantic tokens (light/dark) |
| styled-system/ | Generated Panda runtime (shipped, gitignored) |
| panda.config.ts | Panda CSS design-system config |
| oxlint-panda.mjs | Panda lint rules bound to panda.config.ts |
Common Patterns
Creating a new component
- Create folder
src/components/<Name>/ - Add
index.tsxwith named exports - Add recipe in
../preset/src/theme/recipes/<name>.tsif styling is needed - Register recipe in
../preset/src/theme/recipes/index.ts(inrecipesorslotRecipes) - Re-export from
components/index.tsx - Re-export from
src/index.ts(if not already covered bycomponents/barrel)
Styling
- Use
css()for one-off styles,styled()for styled components, recipes for variants - Colors:
token('colors.brand.500')orcolorPaletteprop + recipe variants - Never use hardcoded colors (
#fff,rgb(...)) — the Panda linter will reject them
Consuming @construkt-kit/ui in apps
ui is a Panda design system. Apps run @pandacss/dev 2.3 or later and point designSystem at it; the manifest
supplies the preset, preflight, JSX framework, every recipe variant and ui's extracted styles:
import { defineConfig } from "@pandacss/dev";
export default defineConfig({
designSystem: "@construkt-kit/ui",
include: ["./src/**/*.{ts,tsx}"],
outdir: "styled-system",
});designSystem takes one package, so apps that use @construkt-kit/pages name it instead — pages extends ui.
Do not list either package in include or add the preset by hand. Apps that import their generated styled-system/
through an alias (e.g. @construkt-kit/styled-system) set importMap to that alias.
The entry CSS keeps the usual layer line:
@layer reset, base, tokens, recipes, utilities;@fontsource-variable/inter is an optional peer: install it unless the app replaces the preset's globalFontface.
@pandacss/dev 2 depends on @parcel/watcher, whose install script pnpm flags until allowBuilds lists it. Its
prebuilt binaries ship as optional dependencies, so the script can stay off:
# pnpm-workspace.yaml
allowBuilds:
"@parcel/watcher": falsePhones and touch
Two things adapt, each on its own signal:
- Touch density follows the primary pointer,
(pointer: coarse), at any width, so a tablet in landscape gets it too. Controls keep their desktop height; menu rows and checkbox, radio and switch rows grow to 44px, text inputs to 16px text (iOS zooms into anything smaller), and buttons get a 44px hit area without changing their layout, narrowed beside a neighbouring button._hoverstyles apply only where the device can hover. It is all CSS on the--control-*knobs, so the first server render is already right. - Presentation follows the viewport. Below
md,Select,ApplySelect,TagSelect,Menu,Popover,DatePicker,DatePickerSelect,ColorPickerandComboboxopen as a bottom sheet that swipes away; frommdup they stay anchored. PassmobileLayout="popover"to keep one anchored; an overlay withautoFocus={false}(a hint beside a focused field) and aninlinepicker always stay anchored. A sheet applies the overlay'scloseOnEscape,closeOnInteractOutsideand outside/Escape handlers. A submenu opens inside its parent's sheet behind a back row (Menu.Content'sbackLabel). A phoneComboboxfield is a button that opens a full-height sheet with the search input on top.Dialogsizesmd,lg,xlandcoverfill the screen belowmd;xsandsmstay centred.
Sheet is the bottom Drawer with a grabber, snapPoints, safe-area padding and a Header that takes
startElement/endElement. Every Drawer placement swipes to dismiss on touch, and a mouse drags it by the grabber
(draggable on Drawer.Content lets the mouse drag the content too); for Drawer.RootProvider, pass the placement
to useDrawer, which sets the swipe direction and the provider's styling, and give Sheet.RootProvider useSheet().
On a coarse pointer a Drawer or Sheet opens with focus on itself rather than its first field, so the on-screen
keyboard stays down; mark a field data-autofocus or pass initialFocusEl to focus it. A DataTable in
mobileLayout="cards" applies its Sort and Filters sheets on Done; dismissing a sheet discards its edits.
Tooltips and hover cards never open from touch. Keep tooltip text a label for something already labelled (an icon
button's aria-label), put anything a phone user needs in ToggleTip or InfoTip, and make HoverCard content
reachable another way.
Give the app <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">.
viewport-fit=cover lets sheets and ActionBar pad for the home indicator. Leave out maximum-scale and
user-scalable=no: they block pinch zoom, and 16px inputs no longer need them.
Upgrading from 0.11
- On a coarse pointer, controls keep their desktop height instead of growing to 44px; buttons keep their 44px hit
area and menu, checkbox, radio and switch rows stay 44px. An app that sized controls up for touch (
mdon touch,smon desktop) can drop that and pass one size - Apps need
@pandacss/dev2.3 or later. Remove the@construkt-kit/ui/layers.cssimport from the entry CSS: Panda emits the recipe sublayer order itself
Upgrading from 0.10
DataTablePropsis a union onclientSide: a server table still passestotalItems. Code that reads the server props picks that branch withExtract<DataTableProps<T>, { totalItems: number }>- New:
DataTableclientSidefilters, sorts and pagesdataitself byparams, from each column'smeta.type, and takes nototalItems. A select filter withoutselectionslists the column's values across every row. A column'sfilterFncan nameinput,select,numberordate, and itssortFnalphanumeric,text,datetimeorbasic. A select column sorts by itsselectProps.getItemLabellabels. Onlyundefinedcells sort last;nullsorts as a value.parseNumberFilterandmatchesNumberFilterare exported for a custom numberfilterFn - New:
labels.itemnames the count when one row matches; without it a customlabels.itemsis used for both
Upgrading from 0.9
- On phones and touch screens, see Phones and touch:
_hoverstyles apply only where the device can hover, coarse-pointer controls and rows are 44px with 16px input text (drop app-level--control-base-hoverrides), and belowmdthe pickers open as bottom sheets — passmobileLayout="popover"to keep one anchored — whileDialogsizesmdand up fill the screen Draweris built on Ark's drawer: every placement swipes to dismiss on touch,Drawer.RoottakessnapPoints, and a bottom drawer stops at 90dvh with a rounded top and safe-area padding.Drawer.RootProvidertakes its placement fromuseDrawer({ placement })instead of aplacementprop- On phones
Combobox.Inputrenders a button that opens the sheet, so arefpassed to it attaches to the sheet's input only while the sheet is open _darkoverrides_lightin dark mode, and_hover/_activealso match[data-hover]/[data-active]and never[data-disabled]; see Condition OverridesCarousel.Rootpaddingis the carousel's own, a CSS length that insets the slides, not a style prop; pad the root withpActionBar.Contentfloats in a fixed bar at the bottom centre of the viewport instead of a popover positioner, soActionBar.Rootdropsplacementandpositioning. The bar is a region labelled "Selection actions" (passaria-labelto rename it) rather than a dialog, and no longer closes on an outside click or takes focus when it opens- Solid
green,orange,yellow,tealandcyan, and sosuccessandwarning, take dark text and hover lighter in light mode: white text on them fell below WCAG AA - While
loading,SubmitDialog,DeleteDialogandSubmitFormdisable Cancel and the close button, and the dialog ignores Escape and outside clicks Calendar,DatePickerandDatePickerSelectdefaulttimeZoneto the user's zone instead of UTC, which moves "today" and the range presets for users away from UTC- Under SSR,
ColorModeProviderrendersdefaultModeand switches to the stored mode once hydrated - Ctrl/Cmd+A in a multiple
ListboxorSelectselects the enabled items the search shows and keeps hidden selections, like the select-all button TreeSelectList: Space checks a row and Enter a leaf. A branch checkbox toggles only the checkable, enabled leaves under it, a branch with none has no checkbox, and select-all skips disabled leaves- Drags leave out nodes
isNodeDraggablerejects, and nesting into a lazy branch that hasn't loaded its children is refused;applyTreeDroptakesisBranchLoadedfor branches you know have loaded Tooltipwith an emptylabelrenders only its children, and text children get a<span>trigger.ToggleTipputs text children in a buttonDataTablekeeps the page when a filter reset changes nothing, and astylefromgetRowPropsno longer replaces the row's grid columnsDataTablemoves to the last page throughonParamChangewhenparams.pageis past it once loading endsPagination.Roottakespage, which used to go to CSSuseFileSelectleaves eachParsedFile.sourceto you: revoke it withURL.revokeObjectURLonce you are doneDisplayValueshows the dash whenformatValuereturnsnullorundefined.formatValueis required unless the value is a string, number, bigint, boolean or an array of those, soDateand object values need one, andDisplayValuePropsis a type aliasNumberFilterreads a single comma as the decimal separator (2,5) unless exactly three digits follow it (1,000stays invalid)VirtualScrollAreacounts the content above the list, such asheader, in eachvirtualItem'sstartandend- Invalid
Checkbox,CheckboxCard,RadioGroupandRadioCardGroupcontrols show a red ring in every variant instead of a red fill on solid only Fieldset.Rootstacks its children at every width, with the root's gap below the legend- Pinned and sticky
Tablecells take their background from--table-row-bg: set it instead ofbgon a row so its pinned cells match - New:
useComboboxforCombobox.RootProvider,VirtualScrollAreascrollToIndexRefandkeepMountedIndex, and color roles forpurple,teal,cyan,pink,yellow,grayandslate, which fell back tobrand
Upgrading from 0.8
TooltipandToggleTiptakelabelin place ofcontent, andInfoTiptakes its text as children. Panda readscontenton any component as the CSS property, so a stringcontentemitted invalid CSS and a colon in it failed the buildImport
css,cx,styled,tokenand Panda's types from the app'sstyled-system/(see Panda CSS Utilities); Panda never extracted styles written with ui's copiesSelectandSubmitDialogcontentWidthapply inline and take px numbers, size tokens or CSS lengthsRenamed for the same reason as
content:DataTablecolumns→columnDefs,LoadingOverlayfill→backdrop,SubmitDialogwidth→contentWidth,TreeSelectListmaxHeight→listHeightRemoved, because Panda never extracts styles nested in an object:
| Removed | Use | | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | |
contentProps,listPropsonSelect,ApplySelect,TagSelect|contentWidth,listMaxHeight,contentCss,listCss| |contentPropsonListbox,ScrollArea,VirtualScrollArea|contentCss;listMaxHeightonListbox| |listboxPropsonSelect,ApplySelect,TagSelect| style props on the select | |buttonPropswidths | nothing: the trigger fills the select | |TooltipcontentProps|Tooltip.Content| |PasswordInputcontainerProps| style props,classNameandstyleonPasswordInput; they style the group | |SkeletonTextrootProps| style props onSkeletonText, which style the stack;lineCssstyles each line | |ApplySelectTrigger,ApplySelectContent|ApplySelect.Trigger,ApplySelect.Content|Removed from the public API:
| Removed | Use | | ------------------------------------------------------- | ---------------------------------------------------------------------- | |
Divider,DividerProps|Separator,SeparatorProps| |CheckboxCardIndicator|Checkbox.Indicator| |Context(Tooltip's root-level export) |Tooltip.Context| |FileUpload.List|FileUpload.ItemsinsideFileUpload.ItemGroup| |Select.EmptyState,ApplySelect.EmptyState|emptyMessageon the select | |ApplySelectRoot,ApplySelectActions|ApplySelect.Root,ApplySelect.Actions| |ApplySelect{Actions,Trigger,Content,Search,List}Props|ApplySelectActionOptions,Select{Trigger,Content,Search,List}Props| |ListboxItemRenderProps|SelectionItemState| |inputGroupButtonSize,InputGroupSize|inputGroupButtonSizeFor(size)| |DraggableTreeNodeRenderDetails|TreeNodeRenderDetails| |TreeSelectListSearchMode|SelectionSearchOptions["mode"]| |dataTableFeatures|createDataTableColumnHelper; theDataTableFeaturestype stays |The tree helpers
filterTreeCollection,collectLeafValues,collectBranchValues,collectCheckedLeafValues,collectBranchesWithLeafDescendantsandmergeFilteredValueare gone; useTreeCollection'sfilter,getDescendantValuesandvisitGone with no replacement:
useRowSelection, and the internalsgetItemMode,ItemMode,isDescendantValue,isLastChildOfParent,useTreeDndContext,TreeRowIndentGuides,DataTableInstance,DataTableHeaderType,DataTableCell,DataTableTableMeta, and DatePicker'stoArkValue,toArkDefaultValue,fireValueChangeandfireClearNew exports:
createDataTableColumnHelper,createListCollectionanduseListCollection(0.8's build dropped both), and the typesManagedSelectionProps,TooltipContentProps,TreeCanDropDetails,ColumnFilterType,SelectionMode,CalendarModeandHiddenInputProps, whichinputPropsonCheckbox,CheckboxCard,RadioandSwitchtakesThe remaining objects of props (
triggerProps,buttonProps,scrollAreaProps,submitButtonProps,NumberFilterinputProps, whatgetRowProps/getItemPropsreturn, columnmeta.selectProps) take no style props.className,style,data-*andcolorPalettestill workcontentCssstyles the dropdown ofSelect,ApplySelectandTagSelectand the scrolled content ofListbox,ScrollAreaandVirtualScrollArea;listCssstyles theSelectfamily's list. Write them as JSX literals and compose with arrays (contentCss={[base, contentCss ?? {}]}), never spreads, which Panda cannot extractSelect,ApplySelect,TagSelect,DataTable,DatePickerandDatePickerSelecttake style props for the whole control (<Select w="200px">)SearchInputandPasswordInputput style props,classNameandstyleon the group, so the icon and buttons stay inside a sized field.PasswordInputdropstype, which its visibility toggle ownsSearchInput,Calendar,DatePickerandDatePickerSelecttakeclearLabelto name their clear controlsTooltip.Contentrenders its own portal and positioner likePopover.Content: drop a wrappingTooltip.Positioner, and passportalled={false}to render in placeplacementonPopover,Menu,HoverCard,ToggleTipandTooltipsurvives apositioningprop, which used to replace itVirtual lists cap at the listbox's
maxH(24rem) like other lists, andApplySelecthonours acontentWidthbelow its 256px minimumApplySelect.Rootautofocuses its search likeApplySelectSubmitDialogandSubmitFormtakeloadingandsubmitDisabledin place ofisSubmitLoadingandisSubmitDisabled, andSubmitDialogtakesopenin place ofisOpen, matchingButton,DataTableandSelect.SubmitFormtakessubmitLabelandcancelLabellikeSubmitDialogPut links inside
Menu.Item asChildrather than wrappingMenu.Itemin a link, which put the<a>between the menu and its item. Enter on a link item clicks it as a pointer does, so a router link's handler runs and the menu closes; passnavigatetoMenu.Rootto route byhrefinsteadMenu.TriggerItemno longer takesvalueordisabled, which zag never read; drop them. It takesiconin place ofstartIcon, likeMenu.ItemMenu.CheckboxItem,Menu.RadioItemandMenu.TriggerItemno longer takeasChildScrollAreawithscrollbars="vertical"sizes its content to the viewport and hides horizontal overflow.TreeSelectListscrolls that way, so long labels truncate instead of scrolling sidewaysSingle-mode
onValueChangeonListboxandSelectreceivesV, nevernull; clear by passingvalue={null}. Clearing throughListbox.Context(clearValue(),setValue([])) no longer emitsSelectProps,SelectRootProps,ListboxProps,SelectionProps,CalendarProps,DatePickerPropsandDatePickerSelectPropscover one selection mode, single unless you pass another (SelectProps<T, V, "multiple">,DatePickerProps<"range">), instead of a union of every mode. With style props in the union,OmitandPickon it failed with "Expression produces a union type that is too complex to represent". For the same reasonSelect.Root,DatePickerandDatePickerSelecttake one call signature per mode likeSelect:ComponentPropson them gives multiple mode, and props typed for every mode no longer spread into themListboxdropsdefaultValue, which it always ignoredThe search field in
ListboxandSelectdrives the list: arrows move the highlight, Enter picks, and typing highlights the first match. The list element takes the listbox content id, so set a custom one through the root'sids.contentA custom search
filteronListbox,SelectorTreeSelectListreceives the trimmed, lowercased query and isn't called while the query is blank.search.mode: "external"shows the items as given, soonQueryChangecan filter them server-sideSelect.SearchandApplySelect.Searchno longer takeonClear, which the field always overrodeTagSelecttakesplaceholderin place oftagPlaceholder. An emptyTagSelectorApplySelectshows the placeholder, "Select item" by default (ApplySelectshowed "Select")ApplySelect's select-all reads "Select all"/"Deselect all" instead of "Select All"/"Clear All" and acts only on the enabled items the search showsTreeSelectListPropsis a type alias; replaceinterface X extends TreeSelectListProps<T>with an intersectionTreeSelectListtakessearch={{ placeholder, filter, showInput, query, defaultQuery, onQueryChange, mode }}in place ofsearchPlaceholder,searchPredicate,showSearch,query,defaultQuery,onQueryChangeandsearchMode, likeListboxandSelect.listHeightalso takes px numbersWhile a query is active,
TreeSelectListexpands every branch with matches and disables drag-and-drop. Collapsing a branch mid-search lasts until the query changes and skipsonExpandedChange; passexpandOnSearch={false}to keep the stored expansionTreeSelectListselect-all acts on the displayed leavesisNodeCheckableaccepts: during a search it adds or removes only the matches, and it keepsvalueids that aren't in the collection. During a search, a matching branch with no matching children can't be checkedTreeViewDndProviderreorders the collection of theTreeView.RootorRootProvideraround it: move it inside, and drop itscollectionandindentPerLevelTreeDropInstructionis ui's own union, withoutinstruction-blockedorindentPerLevel.TreeDropIndicatordropscolorPalette, anduseTreeNodeDnddropsdragHandleRefKeyboard reordering moves every node
getDragValuesreturns, like a pointer drag, and indent inserts the node first in the target branch and expands it (moveNodeByKeyboardinserts first too). Drags skipgetDragValuesids missing from the collection instead of cancellingBuild
DataTablecolumns withcreateDataTableColumnHelper<T>()instead of TanStack'screateColumnHelper<DataTableFeatures, T>(). Widths come only frommeta.widthandmeta.minWidth: columnsize,minSize,maxSizeandenableHidingwent with TanStack's sizing and visibility featuresDataTablesorting or filtering returns to page 1, an emptied search filter applies at once, and a malformed date filter param is cleared. A click or Enter on an input, select, label or other control in a clickable row doesn't fireonRowClickDataTable's sorter is a button named "Sort by" plus the column name, the date filter's placeholder reads "Filter by" plus the column name like the others, and mobile cards render non-string headers.DataTableLabelsaddssortBy,clearSearch,clearNumberandclearDateA
Calendar,DatePickerorDatePickerSelectgivenvalue={undefined}is controlled and shows no date; omitvalueto leave it uncontrolled.maxSelectedDatestakes effectClipboard.IndicatorandClipboard.CopyTextrender the children you pass in place of their default icon or "Copy"
Upgrading from 0.7
- Replace
createConstruktPandaConfigwithdesignSystemas above - Drop
@construkt-kit/presetfrom app dependencies and SSRnoExternal; ui ships it aspanda/preset.mjs - Set
importMapif the app imports itsstyled-system/through an alias - Recipe properties now outrank the
textStylevalues they apply; see Theme Architecture - Only real style props are extracted. Panda 1 also read literals passed to components whose name contains a recipe's
(
columns={[{ width: "320px" }]}onStatsTable), sowidth={column.width}got CSS by accident; usestylefor values that only exist at runtime
Testing
- Runner: Vitest with jsdom environment
- Setup:
vitest.setup.tspolyfillsResizeObserver,IntersectionObserver,scrollToandmatchMedia(not in jsdom) and registers Testing Library'scleanupafter each test - Aliases:
@→srcinvitest.config.ts;#styled-system/*resolves throughpackage.jsonimports - Libraries:
@testing-library/react+userEventfor component interaction tests - Pattern: Tests verify non-obvious UX (e.g., interactive descendants in Select items don't trigger selection)
Ark UI MCP
Use the Ark UI MCP tools to look up Ark UI component props and examples when implementing or customizing components. Components are built on Ark UI primitives, so these tools are relevant for understanding component behavior, props, and theming at the primitive level.
