Shared
Styling and theming
Shared theme tokens, component styling, and control replacements.
On this page
Theme and styles
Import @mendylanda/ui/styles.css once. The package reads your shadcn semantic variables, including --background, --foreground, --popover, --primary, --secondary, --muted, --accent, --border, --input, --ring, and their foreground counterparts. Values must be complete CSS colors, such as hsl(210 40% 98%) or oklch(0.97 0 0). --radius: 0px gives square corners. Typography inherits from your application.
Internal class names and animation names are private to the package. Use the named styling points or data-slot attributes instead of targeting internal utility classes.
Package CSS is in cascade layers with utilities in components, so your Tailwind utilities or unlayered CSS can override it. Your build only needs to generate classes used by your application, not scan the package.
Table styling
Table uses the same semantic colors and radius as Filters. Pass className to style the grid container, rowClassName(row) for application row classes, and isRowHighlighted(row) for the built-in highlight. Use column render callbacks for badges, links, and controls. contentClassName is an adapter for application-owned cell content, not package controls.
Prefer rowHeight, column size/grow, and layout for geometry instead of overriding the grid's positioning CSS. Changing its layout styles can interfere with sticky columns and virtualization. See layout and sizing.
Filter styling
Supply named classes on one bar:
<FilterBar
filters={filters}
classNames={{
search: "sm:w-[420px]",
menu: "[--mendy-filter-menu-width:560px] [--mendy-filter-list-width:240px]",
menuRow: "min-h-11",
chip: "bg-accent",
option: "py-3",
}}
/>The available parts are root, search, searchInput, menuTrigger, menu, menuList, menuRow, menuHeader, editor, chip, chipTrigger, chipRemove, option, and clear. Named classes reach portalled menus too. For shared defaults, pass classNames to MendyUIProvider. Nested providers merge their settings, and bar-level classes override the matching provider setting.
Chip editors and desktop menu editors default to 19rem. Set --mendy-filter-editor-width on your theme root to change both. For one bar, set the variable through both classNames.menu and classNames.editor, since the popups are portalled. Widths stay within the viewport.
Desktop editors are row-aligned by default. Use <MendyUIProvider menuLayout="connected"> for the full-height attached layout. Both switch to one panel with Back on phones.
Chip editor popups open instantly by default. Opt into their entrance animation with <MendyUIProvider editorAnimation> or set animate on a low-level FilterEditorContent. Chip entrance animations are independent of this setting.
HSL channel themes
Older shadcn themes may store color channels rather than complete colors. Capture the host tokens before mapping them on package elements. For example:
:root,
.dark {
--app-background: var(--background);
--app-foreground: var(--foreground);
--app-popover: var(--popover);
--app-popover-foreground: var(--popover-foreground);
}
[data-mendy-ui] {
--background: hsl(var(--app-background));
--foreground: hsl(var(--app-foreground));
--popover: hsl(var(--app-popover));
--popover-foreground: hsl(var(--app-popover-foreground));
}Apply the same mapping to the other semantic colors your theme defines: primary, secondary, muted, accent, destructive, border, input, ring, and their foreground counterparts. Include both light and dark theme declarations. Portals inherit from the document unless portalContainer is supplied. Custom editors styled by the host's Tailwind 3 utilities need their original channel tokens restored inside their wrapper; package controls expect the mapped complete colors.
Render option content
renderOption receives the original, inferred item type. The library keeps the selection control, keyboard interaction, disabled state, and accessible name around your content.
const definitions = defineFilters({
owner: filter.options({
label: "Owner",
options: people,
getValue: (person) => person.id,
getLabel: (person) => person.name,
renderOption: (person, { selected }) => (
<span>
{person.name}
<small>{selected ? "Selected" : person.team}</small>
</span>
),
}),
});renderSummary changes chip contents. renderEditor replaces the field editor and receives typed values, draft setters, apply, and close methods. These callbacks work with local, remote, and externally managed options. See the system reference.
While selected option labels are unresolved, chips show a skeleton, including fields with renderSummary. Known labels stay visible during refetches. For externally managed queries, pass loading until the query settles; missing values fall back to their IDs after loading finishes. Custom summaries should use the supplied choices for labels so they also retain resolved labels during refetches.
Replace a control
Use a provider when an application has its own input, button, label, textarea, or calendar:
import type { MendyUIComponents } from "@mendylanda/ui/filters";
import { MendyUIProvider } from "@mendylanda/ui/filters";
import { Input } from "./my-input";
const components = { Input } satisfies Partial<MendyUIComponents>;
<MendyUIProvider components={components}>
<FilterBar filters={filters} />
</MendyUIProvider>;Define replacements outside render. Forward the supplied ref, className, attributes, and event handlers to the actual control. Button replacements also need to honor asChild, variant, and size. Calendar replacements use the exported Calendar props. A replacement that drops these contracts can break focus or selection behavior.
Default controls are exported from @mendylanda/ui/primitives, or individually from paths such as @mendylanda/ui/primitives/button. You can wrap one of these defaults in your replacement without recursively invoking the provider replacement.
Local themes and dialogs
Dropdowns mount to document.body by default, so they inherit the document theme. If your theme is scoped to part of the page, supply an element inside it:
const [container, setContainer] = useState<HTMLDivElement | null>(null);
return (
<div ref={setContainer} className="project-theme">
<MendyUIProvider portalContainer={container}>
<FilterBar filters={filters} />
</MendyUIProvider>
</div>
);The same setting keeps menus inside a containing dialog when required. React provider settings continue through portals.
Compose a different layout
FilterList and FilterBar include a trailing "Clear all" action by default whenever filters or search are active. It clears removable filters and search. Set showClear={false} to hide it. To place the action elsewhere, use <FilterList showClear={false} /> and render <FilterClear /> in that location.
FilterRoot supplies the shared controller to FilterSearch, FilterMenu, FilterList, FilterField, FilterClear, FilterFeedback, and FilterFieldEditor.
<FilterRoot filters={filters}>
<FilterMenu asChild>
<button type="button">Choose filters</button>
</FilterMenu>
<FilterList />
<aside aria-label="Owner filter">
<FilterFieldEditor id="owner" autoFocus={false} />
</aside>
<FilterFeedback />
</FilterRoot>FilterField renders one chip or suggestion by ID. FilterFieldEditor renders the corresponding editor in your layout, including dynamic options and custom editor callbacks. An embedded editor stays mounted in its container; closing a popover does not remove it. Your layout controls its visibility.
For a completely different UI, use useFilters, useControlledFilters, or useFilterController without FilterBar. Pure definitions, bindings, codecs, paste classification, and state helpers are also exported from @mendylanda/ui/filters/core. Importing that path does not load UI controls or the URL adapter.
Language and direction
MendyUIProvider also accepts locale, messages, and dir for Table, Filters, and Sheet. See Localization for English, Hebrew, and private dictionaries.