- After making changes, never run
templ generate/go tool templ generateand never manually rebuild*.min.jscomponent assets, since both JS minification and generated_templ.gofiles are handled automatically by the normal development workflow (task dev/ watchers).
Shared plans live in plans/. Read plans/README.md before working on one.
Every component is the 1:1 pendant of shadcn's bases/base/ui on Base UI, at the pin in plans/UPSTREAM.md. Three rules:
- Go props are Base UI props. Same names in Go casing (
Open,DefaultOpen,SideOffset), plus the props shadcn's wrapper adds (Variant,Size,Spacing) and the pass-throughID,Class,Attributes. Value props only, no callbacks or refs. Only what a component or example uses; add a prop under its Base UI name when it is needed. A prop that is not in the Base UI reference does not exist. Controlled means theOpenorValuepointer is set, uncontrolled meansDefaultOpenorDefaultValue. - The DOM is shadcn.
data-slotas shadcn renders it, the state attributes Base UI renders (data-open,data-checked,data-disabled,data-side, ...), role and ARIA as Base UI.data-horizontalanddata-verticalin class strings are Tailwind variants fordata-orientation(assets/css/shadcn-tailwind.css), never attributes. The JS finds parts bydata-slotand reads state from those attributes. A trigger finds its popup or panel through the port markerdata-templ-controls(the link Base UI keeps in its store), because Base UI rendersaria-controlsonly while the popup or panel is open, and a tooltip trigger none at all. That also covers a part merged onto another component's element (therenderprop pendant, e.g. a trigger onsidebar.MenuButton): the part's attributes carry its slot and win (if _, ok := p.Attributes["data-slot"]; !ok), only an element of a server component example keeps its own slot (serverRendered). Tooltip and hover card triggers name their popup indata-templ-tooltip-triggeranddata-templ-hover-card-trigger, because one element can be a tooltip trigger and another component's trigger at once. Never select bycn-*classes, the site's inliner flattens them to utilities. No other selector attribute. - A Base UI prop that is never rendered is
data-templ-<prop>. The kebab case of the Base UI prop name, on the element of the part that receives it (data-templ-side-offset,data-templ-close-delay). A name that is not a Base UI prop is not allowed. Port markers React keeps in memory (portal node, owner links) use the same prefix.
The project prefix is templ everywhere: window.templ for the shared JS API, _templ* for element expandos. The scroll lock sets Base UI's own data-base-ui-scroll-locked.
Every example is upstream's example 1:1 in structure, classes and DOM. Its people, avatars and texts stay in the Go and templ universe (@templ, a-h, axadrn, joerdav and their GitHub avatars) where upstream shows shadcn, Vercel or Next.js ones. Keep them, never swap in upstream's; the parity runs list them as accepted pixel differences (plans/UPSTREAM.md).