HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Theming
Overriding a componentEvery Helix component draws inside a shadow root, so your stylesheet cannot
reach the elements inside it. Your rules cannot break a component's internals
by accident, and we can rewrite a component without breaking your page.You can change a component's styles in three ways. Try them in the order below.1. Tokens:root{--helix-seed-radii:2px;}To make every card, dialog, and button squarer, change a token. Set the seed.
The four radius steps, such as --helix-radii-md, are derived from it. md is
one step of the four, and buttons and dialogs use a different step. See
Shape and borders.Custom properties inherit, so tokens cross shadow boundaries. One declaration
reaches every component at once.Try tokens first. A request to restyle the button is often a brand difference,
such as less rounding. A token is the smaller change, and it keeps every
component consistent.See Tokens and Deriving a theme.2. Per-component hookshelix-card{--card-padding:var(--helix-space-lg);}helix-accordion{--accordion-border-color:var(--helix-color-border);}A hook is a custom property that a component publishes and reads. It defaults
to a semantic token, so a hook you leave unset uses the themed value. Setting a
hook changes that one component.Hooks are component API. We check that they stay separate from tokens. A
hook is named after its component, such as --card-padding or
--drawer-radius. A token is named --helix-*. We also check each hook in both
directions:A rule in the component reads every documented hook. A hook that is declared
and never read would do nothing.Every hook that a rule reads is documented. Otherwise you would not know the
hook exists.Each component's page lists its hooks under Custom properties.3. Partshelix-select::part(trigger){font-variant-numeric:tabular-nums;}helix-dialog::part(surface){max-inline-size:40rem;}::part() selects a named element inside the shadow root, and you can write any
declaration on it. Parts give you the most control. Try them last, because they
tie your stylesheet to a component's internal structure.Every documented part is rendered. We check parts in both directions. A
documented part must be rendered, and a rendered part must be documented. A
documented part that nothing renders would be the worse failure for you. A
::part() that matches nothing is still valid CSS, so you get no error.Each component's page lists its parts.Parts of a component inside another component::part() crosses only one shadow boundary. When a component renders another
component, the child's parts would be out of reach. Every such component
forwards its child's parts, and each forwarded name starts with the role the
child plays:helix-date-input::part(picker-cell){… }helix-select::part(listbox-items){… }helix-video::part(overflow-header){… }<helix-date-input> renders a date picker in the picker role, so the picker's
cell part is picker-cell. The tooltip inside <helix-copy-button> is in the
tooltip role, so its surface is tooltip-surface and its arrow is
tooltip-arrow. When the child's part is the role itself, the forwarded name is
the role alone: each line inside <helix-log> is line.A part that changes with a state has a second name while the state is active,
and that name is forwarded too. A tooltip placed below its button is also
tooltip-surface-bottom, and its arrow is also tooltip-arrow-bottom. Your
stylesheet can use these names to style each side, which it cannot detect any
other way. The arrow already faces the button on every side. Use the names for
styles you add on top.The prefix keeps part names unique. A component and its child can use the
same word. This happened once: Command forwarded the listbox's search-field
under a name it also rendered itself, so ::part(search-field) matched two
elements at once.We check forwarding in two ways:helix component's parts matches every published part with a rendered one,
and every rendered part with a published one.helix component's exportparts requires a composed component to forward
every part its child publishes, under one prefix, with no collisions.As a result, you can reach every part the color picker publishes from inside a
color input, and every part the date picker publishes from inside a date input.If you find a part you cannot reach, report it to us. The missing part is a bug.Part names shared by every componentSome parts name a role. Every component uses the same fixed set of role names,
so you learn each name once:PartWhat it isboxThe drawn control, where the host is not it.containerAn element's own outer box.contentA region you fill.panelA region switched between, or a sheet anchored to an edge.surfaceA floating panel drawn over the page, such as a menu, a popover, a tooltip, or a dialog.viewportThe box that shows part of a larger area: a scrollport, or the frame a map or a graph is panned and zoomed in.A part that names something specific to one component, such as thumb,
swatch, or weekday, is listed on that component's page.What not to doDo not raise specificity to override a component.helix-button.thing.thing
is still outside the shadow root, so it reaches nothing that helix-button did
not already reach. The shadow boundary blocks your rule, and selector weight
cannot change that.Do not restyle through !important on the host. Some properties inherit
into a shadow root and most do not. !important on the host works for color
and font-family and fails with no error for everything else. A silent failure
is harder to find than an error.Do not fork the component to change a color. The headless tier is for when
you want none of our styling. Changing one value does not need it. See
Tiers.Replace all of the stylingimport'@fusion.dev/helix-headless'The headless tier provides structure, accessibility, state, and form
participation, with no CSS. You write the entire appearance. Use it when your
design is entirely different from ours. The headless tier is fully supported.
For example, helix-headless-select is a registered element that you can use
directly.