HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Essentials
Localization and RTLRight-to-left layoutSet dir, and the layout flips:<htmldir="rtl"lang="ar">Components are written with logical properties, such as inline-start for
left and margin-block for margin-top. Helix does not detect the direction.
The components follow the dir that your document sets.Physical properties appear only where the position is physical. A floating
panel is placed by coordinates measured from the viewport's left edge, so the
panel and its arrow use left and top. Everything you would expect to mirror
still mirrors.ChartsA category or time axis starts on the same side as the text. On a right-to-left
page, the first value is on the right. The drawing, its labels, its hover
targets, and its tooltips all mirror together. The vertical axis does not
change, and numerals are not reversed. Two charts keep their direction:A whole donut runs clockwise from twelve o'clock, following the convention of
a clock.A scatter chart keeps its left-to-right plane, because both of its axes are
measures.See Charting for the rest.Carets and arrow keysThese carets point along the line of text:a caret that means next or previousthe caret between two breadcrumbsthe caret on a closed disclosureOn a right-to-left page, they point the other way. Every arrow key moves in the
direction it points on screen. On a right-to-left page, the Left Arrow key:moves to the next tabmoves to the next daymoves to the next control in a toolbaropens a tree's branch or a menu's submenu, whose caret points leftA slider and a splitter's divider move in the direction of the key.Media players and pin inputsThese two components run left to right on every page.A media player's timeline and skip buttons run left to right in every script,
like a tape, so the Left Arrow key rewinds.A pin input's boxes run left to right, because a code is read like a phone
number. The first character you type goes in the leftmost box, a pasted code
fills to the right, and the Right Arrow key moves to the next box. The pin
input's label, hint, and error follow the direction of the page.Floating panel placementIn placement, the side is physical and the alignment is logical:right is the right side of the anchor in both directions.bottom-start aligns a panel with the anchor's start edge, which is its right
edge on a right-to-left page.A panel with no room on its side flips to the other side. A right-start panel
beside a rail at the start of a right-to-left page opens to its left.Your own stylesUse logical properties in your own styles, so your layout flips with the
components. Styles written with left and right do not flip.Locale-aware controlsSix families format numbers or dates and take a locale:Date Input, Date PickerDate formats, and the first day of the week where first-day-of-week is not setTime Input, Time Picker12 or 24 hour, separators, and the AM and PM on the wheelsPaginationGrouped thousands in page numbersFile InputFile sizes<helix-paginationlocale="de-DE"pages="1200"></helix-pagination>If you do not set locale, these controls use the nearest lang attribute,
then the browser's locale, which is usually correct. Set locale when your users choose your application's
language separately from the language of their operating system.The first day of the week comes from Intl.Locale's week information, which
Firefox does not provide. There, and where you set no locale, the week starts
on Sunday. Set first-day-of-week to choose the day yourself.locale formats dates, times, and numbers. On a family that reads the words
registry, it also picks the words. On the others, set the words a control
speaks through their properties, such as previous-label and next-label on
Date Picker and Date Input. See
Translate the built-in words. A time wheel's
name, such as "Hour", comes from the locale.Formatting uses Intl, so Helix bundles no locale data, and you have nothing to
keep up to date.CalendarsDate Picker, Date Input, and Format Date write dates in five calendars. Each
has the Gregorian months, so the days in a Date Picker are the same in all
five:CalendarOctober 6, 2026The locale's own calendar forgregoryOctober 6, 2026Most localesbuddhist6 ตุลาคม 2569th-THjapanese令和8年10月6日ja-JP-u-ca-japaneseroc民國115年10月6日zh-TW-u-ca-rociso86012026 October 6A locale written with -u-ca-iso8601If you do not set calendar, these components use the locale's own calendar
when it is one of the five. Set calendar to choose one yourself:<helix-date-pickercalendar="buddhist"locale="en-US"></helix-date-picker>Every other calendar is written as Gregorian, in the locale's language and
digits. This includes the Persian, Islamic, Hebrew, Ethiopic, Indian, Coptic,
Chinese, and Dangi calendars, whose months are not the Gregorian months. A page
in fa-IR, whose own calendar is Persian, shows October 2026 as "اکتبر ۲۰۲۶".The value is YYYY-MM-DD in every calendar. In a segmented Date Input, the
segments hold the Gregorian day, month, and year, so a Buddhist date is typed
with the year 2026.Translate the built-in wordsComponents have English words built in: the names a screen reader hears, the
sentences a live region says, and the text on buttons such as "Load more".
Register the words for a language once, and every element in that language
speaks them:import{registerWords}from'@fusion.dev/helix/words'registerWords('pl',{'helix-combobox':{emptyText:'Brak wyników',resultsLabel:'{count, plural, one {# wynik} few {# wyniki} many {# wyników} other {# wyniku}}',},'helix-data-table':{nextLabel:'Następna strona',previousLabel:'Poprzednia strona',},})Each family's words are keyed by its tag, such as helix-combobox, and then
by the property name. The headless and styled tiers share one key.
@fusion.dev/helix/words holds registerWords without the elements, so a page
can register its words before it loads any, and it types each key and property.
A page that uses only the headless tier imports
@fusion.dev/helix-headless/words. With registerWords imported from another
entry, import the types once:importtype{}from'@fusion.dev/helix/words'Every family reads the registry for the words it declares, the words several
families share included. Register a shared word under each family's key: the
name of a dismiss button, dismissLabel, under helix-callout; a collection's
emptyText and loadingText under helix-tree; and the sentence a field
reports when a required value is missing, valueMissingMessage, under
helix-checkbox. A family that has its own English for a shared word, such as
Tag's Remove {label}, keeps it until you register another.The name a family gives itself where nothing else names it is fallbackLabel
in every family, such as { 'helix-table': { fallbackLabel: 'Tabelle' } }.Which words an element usesA value you set on the element, as an attribute or a property. The
English counts as a value, so empty-text="No matches" keeps the English
on every page.The words registered for the element's locale.The English.Set the property to null, or remove the attribute, to return to the
registered words. Reading the property gives the word the element uses, in its
locale.Which locale an element speaksThe element's locale property.The nearest lang attribute, read through shadow roots, such as
<html lang="pl">.The browser's language.The same locale picks the words, the plural forms, and the way numbers are
written, so one sentence never mixes two languages.A word that a language does not have falls back one key at a time, through
the regional, script, and language forms of the tag and then to English. A
de-CH page uses a de-CH word, then a de word, then the English. Hong Kong
(zh-HK) reaches a zh-Hant word before a zh word. Name a set of words by
its full tag where a language has regional forms, such as pt-BR, pt-PT, and
es-419.When you register words, or change the lang of the <html> element, every
element on the page updates once. When you change the lang of another
element, call refreshWords().Write a messageWords are messages in a small part of the ICU MessageFormat syntax, which
translation tools read:SyntaxWhat it does{name}Puts in a value. A number is written in the locale, such as 1.200 in German.{p, number, percent}Writes a number as a percentage, with the sign where the locale puts it. {n, number, integer} rounds.{count, plural, one {# wynik} few {# wyniki} other {# wyniku}}Picks a form by the language's plural rules. # is the number. =0 {...} matches one exact number, and offset:1 counts from one.{n, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}Picks a form for a position.{direction, select, ascending {...} descending {...} other {...}}Picks a whole sentence by a value, so a language can phrase each one its own way.'' and '{'Write one apostrophe, and a brace as text. A single apostrophe is text, so L'élément needs no escaping.Each property's reference entry names its placeholders. A message that does
not parse shows as written, so a typo appears on the page where you can see it.formatMessage(message, values, locale) formats a message for your own
elements, and wordsLocale(element) gives the locale an element speaks.Server renderingA server knows the element's own attributes and the words your server code
registered. It does not know the page around the element. So on a server, and
in the first render while an element hydrates, a word uses the element's
locale, then its own lang, then the locale you set with
setDefaultWordsLocale, then English. After hydration, the element reads the
nearest lang and updates once if its words change.Register the same words on the server and in the browser, before the
elements are defined, so the two renders agree.A server that renders one language calls setDefaultWordsLocale('pl') once.A server that renders several languages sets lang or locale on the
elements.The registry holds no current language, so two requests in two languages in
one process do not affect each other.Set the words of other familiesA value set on the element always wins, so any family takes its words as
properties too, such as empty-text on a listbox. Properties that combine
values are format templates:<helix-pagination-summaryformat="{from} bis {to} von {total}"locale="de-DE"page="2"page-size="10"total="1500"></helix-pagination-summary>Placeholders have names, so a translation can put them in any order. Some
languages need a different order, and a positional format cannot express it.Validation messages are properties too. Each one is named for the validity flag
it reports:value-missing-messagebad-input-messagerange-underflow-messagerange-overflow-messagestep-mismatch-messagetoo-short-messageDate Picker, Date Input, Time Picker, and Time Input also take
unavailable-message, for a date or time that disable-dates, disable-days,
or disable-times rules out.A message appears in the browser's own validation bubble and on the line under
the field. feedback does not reach the bubble. If you do not set a message, a
text field shows the browser's own message, which is already in your user's
language. Every other control shows its English sentence. An empty message
counts as not set.<helix-number-inputlabel="Invités"min="2"range-underflow-message="Au moins {min}."></helix-number-input>Color Picker and Color Input say a color's name to a screen reader, such as
"vibrant red". Set color-words to a JSON table of the words a name is made
of. The keys are the English words, and they match the strings that React
Aria's @react-stately/color translates, so you can reuse that table.<helix-color-pickercolor-words='{"red": "rot", "vibrant": "kräftiges", "colorName": "{lightness} {chroma} {hue}"}'lang="de"></helix-color-picker>Helix ships English words and no translations. Pass the words from your own
translation system, either to registerWords or to the properties.Rise and fall colorsMarkets draw a price movement in different colors:Mainland China, Taiwan, and Japan draw a rise in red and a fall in green.Hong Kong, like most other markets, draws a rise in green and a fall in red.Colors that show a status, such as an error or a warning, are the same in all
of these markets. Only a color that shows which way a value moved changes.Two tokens hold these colors:TokenDefault--helix-color-rise--helix-color-success, which is green--helix-color-fall--helix-color-danger, which is redTrend uses them when you set color-by="movement". Without it, the color of a
trend follows good, which says whether the change is an improvement.For a page that shows one of the markets that draw a rise in red, swap the two
tokens at the root of the page, and set color-by on each trend::root,[data-helix-theme]{--helix-color-fall:var(--helix-color-success);--helix-color-rise:var(--helix-color-danger);}<helix-trendcolor-by="movement"format-options='{"style":"percent","maximumFractionDigits":2}'value="0.0124"><spanslot="label">Shanghai Composite</span></helix-trend>An element with data-helix-theme declares every token again for the part of
the page it themes, so the rule names [data-helix-theme] beside :root. To
color one part of a page, such as a panel of Shanghai prices on a page about
several markets, set the two tokens on that panel.The two tokens point at the status colors, so after the swap each one still
measures at least 4.5:1 on the page and on every surface, in both modes. A
theme that changes success and danger changes them too.Helix does not set these from lang. The language of a page does not name
its market. A page in Chinese for Hong Kong draws a rise in green, a zh-Hant
page with no region could be for Taiwan or for Hong Kong, and a page in English
can show Shanghai prices. If the language of your page does decide its market,
select on the language yourself::is(:root,[data-helix-theme]):is(:lang(zh-CN),:lang(zh-TW),:lang(ja)){--helix-color-fall:var(--helix-color-success);--helix-color-rise:var(--helix-color-danger);}A page in zh-HK matches none of these, so it keeps green for a rise.Other components color a series by its place in the list, or by a tone you
name. To draw one in the color of a rise or a fall, set its own color to the
token, such as --sparkline-color: var(--helix-color-rise) on the sparkline of
a price that went up.Sources:Benzinga, on the colors of stock prices in China, Japan, and TaiwanFeng Jiang, Rui Zhang, and Su Lu, on red and green in the Chinese and Hong Kong marketsWhat to check when you add a languageLong strings. Labels in German and Finnish are often long. A control sized
for a short label may not fit them. Controls take their width from the
layout and set no width of their own, which helps. A toolbar that fit six
buttons may now fit four.The lang attribute. Set it as well as translating the strings.
Hyphenation, quotation marks, and font fallback all depend on it.A market's colors. If the new language comes with a market that draws a
rise in red, swap the rise and fall tokens. See
Rise and fall colors.Numerals.Intl formats the digits. A % or a currency symbol hardcoded
in your own markup does not move to the correct side.Icons that point. A caret that a component draws turns by itself. An icon
you place turns only when you add mirror, because icon names are physical.
Use mirror on an arrow that means forward or back, and it turns on a
right-to-left page.<helix-button>
Continue
<helix-iconmirrorname="arrow-right"slot="end"></helix-icon></helix-button>What Helix does not provideHelix does not include:translations of its words into other languagesa message catalog for your own application's texta date-range formatter, beyond what the controls format themselvesThese belong to your application's i18n layer. You can register every word,
and set every translatable property, from that layer.