HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Essentials
Using the elementsWrite <helix-card> in your JSX. The elements are plain custom elements. There
is no wrapper component, no patched React, and no React package.exportdefaultfunctionPage(){return(<helix-card><h2slot="header">Deployments</h2><helix-buttonemphasis="solid"tone="primary">Ship it</helix-button></helix-card>)}Each section below covers a problem that has cost us time in practice.Bind Helix events with onhelix-*<helix-tabsonhelix-before-activate={(event)=>setPane(event.detail.value)}/>React attaches no custom element listeners while it hydrates. On a
server-rendered page, these props do nothing until React commits an update for
some other reason. Then they start working, and nothing is logged either way.
The bug is facebook/react#35446,
and it is still open. The elements work around it for you.Every Helix element binds the handler itself, just before it dispatches any
event, using the function React already holds for it. We measured this in
Chrome against React 19.2.8. On a hydrated page, the first event reaches the
handler. After React binds its own listener, no event is delivered twice.You do not need to do anything, and nothing changes when React fixes the bug.React's own events work when they bubble. onClick on a <helix-button> goes
through React's synthetic event system, which hydration sets up.A few Helix events are native events relayed from inside the component. The ones
that do not bubble never reach React's listeners:load and error on Avatarended, play, and pause on VideoThe elements bind these for you, so onError on a <helix-avatar> works like
any other handler.Write the prop as on plus the event nameReact removes on from the prop name and listens for exactly the rest, as
written.onhelix-before-open reaches helix-before-open. ✅onhelix-sample-start reaches helix-sample-start. ✅onHelixOpen binds an event called HelixOpen, which is never dispatched. ❌The number of hyphens does not matter. We measured this in Chrome against React
19.2.8.Register the elements in one client componentA custom element registers when you import its module. That import runs only if
it is in the client graph, and a server component's imports do not ship to the
browser. Put every element import in one client component that renders null:'use client'import'@fusion.dev/helix/card'import'@fusion.dev/helix/button'exportfunctionHelixElements():null{returnnull}Render it once in your layout. Your pages stay server components. An
unregistered tag is an unknown element in the HTML, and it upgrades when the
definition loads. You do not need to mark any page 'use client' to use a
button.Import elements.css beside the base layerimport'@fusion.dev/helix-styles/helix.css'helix.css imports elements.css after the primitives, the tokens, and the
base layer. If you import the layers one at a time, import elements.css last.elements.css hides each tag with :not(:defined) until the element upgrades.
It uses visibility, so a hidden element keeps the size of its unstyled
content. The layout settles when the elements upgrade and take their own size.
Without the file, a card appears as unstyled text and then jumps into its card
layout.A data table that serves its first page as rows is the one element shown before
it upgrades. elements.css lays out those rows as the table draws them. See
React.We generate the file from the element sources, and it lists every tag by name.
CSS has no selector for any custom element, and a bare :not(:defined) would
also hide your framework's development overlay.Import from subpathsimport'@fusion.dev/helix/card'// ✅ one familyimport'@fusion.dev/helix'// ❌ the barrelImport one component family per module, so you do not ship the whole library to
use a button.Turn off an attribute that is on by defaultA boolean attribute is off until you write it, unless its default is true. The
property table on each component's page shows the default. An attribute whose
default is true stays on until you write the string "false", such as modal
on Drawer:<helix-drawermodal="false"></helix-drawer>"false" is the only value that turns it off. An empty value, or any other
string, leaves it on. In JSX, modal={false} sets the property and does the
same. Other features that are on by default turn off with an attribute named
no-, such as no-copy.Declarative shadow DOM is opt-inThe elements need no declarative shadow DOM (DSD). Each element attaches its
shadow root and loads its styles with its definition, so you do not need to
configure anything. If you render with Lit SSR yourself, the DSD it writes
hydrates. See Server rendering.DSD does not work in a React server component. React inserts a server
component's output by replaying the flight payload through DOM APIs, without
running the component again, and DSD does not activate for nodes that a script
inserts.