@magnet-js/shadcn
v0.2.0
Published
shadcn-style components for Magnet, built on ZagJS primitives.
Readme
@magnet/shadcn
shadcn-style components for Magnet, built on ZagJS primitives.
@magnet/shadcn is a component library: the shadcn/ui component set ported to
Magnet. Machine-backed components (dialog, select, tabs, and friends) run on
ZagJS state machines via @magnet/zag; purely visual components (button, card,
badge, and friends) apply shadcn's Tailwind class strings directly. Every
component is an auto-curried function that takes the Magnet context first,
follows shadcn v4's markup and data-slot attributes, and reads its props
reactively — the props bag, variant, size, and class may all be signals.
Install
JSR
deno add jsr:@magnet/shadcnnpm via JSR
npx jsr add @magnet/shadcnnpm
npm install @magnet-js/shadcnUsage
Components take the Magnet context first. Create it with @magnet/ui and a
signal implementation, and carry the icon set and the cn class composer from
@magnet/class on it (needed by the class-composed components):
import { tc39 as signal } from "@magnet/signal";
import { cn } from "@magnet/class";
import { magnet } from "@magnet/ui";
import { button, checkbox, lucide } from "@magnet/shadcn";
const m = magnet({ window, icons: lucide, cn: cn(signal), ...signal });
// Pure-CSS component: (props?, children?) after the context; props may be
// signals and re-compose reactively.
const saveButton = button(m, { variant: "destructive", size: "sm" }, [
"Delete",
]);
// Machine-backed component: returns the assembled root element and the
// machine's stop cleanup (ride Magnet's cleanup idiom for teardown).
const [root, stop] = checkbox(m, { id: "subscribe", defaultChecked: true });Multi-part machine components return the running ZagJS service extended with tag functions pre-bound to each part, with shadcn's classes pre-applied:
import { dialog } from "@magnet/shadcn";
const dlg = dialog(m, { id: "confirm" });
const view = [
dlg.trigger(["Open"]),
dlg.backdrop(),
dlg.positioner([
dlg.content([
dlg.closeTrigger()(["Close"]),
dlg.header([dlg.title(["Title"]), dlg.description(["Description"])]),
dlg.footer(["Footer"]),
]),
]),
];
// ... later: dlg.stop()Icons
Components that render glyphs require a full IconSet in their context type
(icons: IconSet); each shipped set provides every canonical glyph, with path
data vendored from the collection at a pinned version. Inject a set at magnet
creation as above, or supply any object satisfying IconSet. Per instance, an
icon option replaces the context default ({ icon: MyIcon }) or suppresses
the glyph ({ icon: false }).
Seven sets are shipped, all covering the same canonical keys:
lucide— lucide, ISC licensed (copyright Lucide Contributors).tabler— Tabler Icons, MIT licensed (copyright Paweł Kuna).hugeicons— Hugeicons free stroke-rounded set, MIT licensed (copyright Hugeicons); the Pro sets are commercially licensed and not vendored.phosphor— Phosphor Icons, MIT licensed (copyright Phosphor Icons).remix— Remix Icon, Apache-2.0 licensed (copyright Remix Icon); attribution preserved in the module header.carbon— Carbon Icons, Apache-2.0 licensed (copyright IBM Corp); attribution preserved in the module header.solar— Solar Icon Set bold style, CC-BY-4.0 licensed (copyright 480 Design); attribution provided in the module header.
All seven modules are generated by scripts/build-icons.ts from the
collections' pinned upstream sources; re-run it to regenerate them.
Managers in the context: theme and locale selectors
themeSelector and localeSelector are self-contained dropdowns composed from
the menu machine and shadcn's DropdownMenu styling. They read their managers
from the Magnet context — theme for the theme manager, locale for the locale
manager — so apps register them once at magnet creation:
import { theme, themeManager, ThemeTag } from "@magnet/theme";
import { locale, localeManager } from "@magnet/locale";
import { localeSelector, themeSelector } from "@magnet/shadcn";
const m = magnet({ window, icons: lucide, cn: cn(signal), ...signal }).extend({
theme: themeManager(m0, [
theme("paper", [ThemeTag.Light]),
theme("ink", [ThemeTag.Dark]),
], document.documentElement),
locale: localeManager(m0, [
locale("en", "English"),
locale("pt", "Português"),
], document.documentElement),
});
// In a nav bar (or a sheet on smaller screens):
themeSelector(m, { names: { ink: "Tinta" } });
localeSelector(m, { tag: true });Both mirror the current selection in the trigger (plus a chevron), lay out their
entries on a shared grid with subgrid columns, and take order and visibility
options for their parts. The theme selector offers a no-theme entry
(sun/moon/system glyphs by theme kind, details.icon overriding per theme) that
clears the pick back to system resolution; display names come from the names
record, falling back to the theme id. The locale selector shows the endonym by
default, with the tag, translated name, and details.icon opt-in per the
options.
Theming
Class strings are shadcn v4's, so the package expects shadcn's Tailwind setup
and theme CSS variables (--primary, --background, and so on) to be present
globally, exactly as a shadcn app provides them. Styled parts carry shadcn's
data-slot attributes (plus reactive data-variant/data-size where shadcn
has them). A user class is composed LAST via cn — joined with spaces, with
no tailwind-merge deduplication, so conflicting classes resolve by the
stylesheet's cascade rules.
Public API
Components (each with its Props/variant types, and multi-part families
exporting their member functions):
- Actions:
button,buttonGroup,toggle,toggleGroup. - Forms:
checkbox,combobox,datePicker,input,inputGroup,inputOtp,label,radioGroup,select,slider,switch,textarea. - Overlays:
contextMenu,dialog,drawer,dropdownMenu,hoverCard,menubar,navigationMenu,popover,sheet,tooltip. - Data display:
accordion,avatar,badge,calendar,card,carousel,table,tabs. - Feedback:
alert,progress,skeleton,toaster. - Layout:
aspectRatio,breadcrumb,collapsible,pagination,resizable,scrollArea,separator.
Icons:
lucide— the canonical icon set, covering exactly the glyphs the components render by default.tabler,hugeicons,phosphor,remix,carbon,solar— the same canonical glyphs vendored from six more collections (see the Icons section above for provenance and licenses).Icon/IconSet— the glyph factory type and the canonical icon keys, for supplying a custom set.
Composition helpers, for building custom shadcn-style parts:
classed— wraps a tag function so it always carries the given classes.withAttrs/withSlot— wraps a tag function so it always carries the given attributes / adata-slot.withGlyph— wraps a tag function so a glyph renders after its children.attributes— composes a pure-CSS component's reactive attributes bag.
Context types:
ComponentContext/MachineComponentContext— the context shapes the pure-CSS and machine-backed components require.PartiallyApplied— the auto-curried component signature (re-exported from@magnet/common).
License
MIT © 2026 Fernando G. Vilar.
