Skip to content

Upgrade guide ​

This version matches Astryx v0.7.1. The previous version matched 0.4.1.

Every section below is a change you can hit while upgrading. Each one says four things: what changed, how you wrote it before, how you write it now, and what to do.

Most projects only need three steps:

  1. Rename labelHidden to isLabelHidden.
  2. Replace enableEscape in useDismiss with dismissOnEscape.
  3. Rename the old target names in your own theme.

Styles and themes ​

reset.css is now a full global reset ​

What changed: @astryx-vue/core/reset.css used to be a single line that declared the CSS layer order. It is now a full global reset, the same kind of thing as Tailwind Preflight (upstream #7355).

Before: you imported it to get the layer order.

ts
import '@astryx-vue/core/reset.css'
import '@astryx-vue/core/styles.css'

Now: every stylesheet declares the layer order on its first line, so reset.css is no longer needed for that. Importing it changes the default look of plain HTML: headings, paragraphs and lists lose their default margins, lists lose their bullets, images and svg become block level, form controls inherit the font and color, buttons lose the browser chrome, and placeholder text is no longer faded.

What to do:

  • Only wanted the layer order: delete the line. Components do not depend on it.
  • Want one consistent starting point across browsers: keep it. Its rules sit in the lowest astryx.reset layer with zero specificity, so your own styles always win.
  • Want one block of the page cleaned up: do not import the file, write styles for that block yourself.

More detail in Installation.

Spacing tokens moved from px to rem ​

What changed: --astryx-spacing-* is now written in rem instead of px, and the numbers were converted. Every theme changed, Matcha and Y2K included (upstream #7359 #7374).

Before:

css
--astryx-spacing-1: 4px;
--astryx-spacing-4: 16px;

Now:

css
--astryx-spacing-1: 0.25rem;
--astryx-spacing-4: 1rem;

What to do: the browser default root size is 16px, so nothing looks different and most projects need no change. Only two cases move:

  • The page sets html { font-size: ... }: spacing and padding now scale with it. That is the point of the change.
  • Your own theme writes spacing in px: those steps stay fixed. Write them in rem if you want them to follow the root size.

More detail in Space, radius, shadow.

Theme target names were renamed ​

What changed: a batch of old target names is gone and the hyphenated names replace them. A camelCase name such as buttonIcon, or a name that starts with a capital such as Card, now throws (upstream #7302).

Before:

ts
defineTheme({
  name: 'brand',
  components: {
    progressbar: { base: { color: 'var(--brand-ink)' } },
    textarea: { base: { borderRadius: '8px' } },
  },
})

Now:

ts
defineTheme({
  name: 'brand',
  components: {
    'progress-bar': { base: { color: 'var(--brand-ink)' } },
    'text-area': { base: { borderRadius: '8px' } },
  },
})

What to do: search the keys under components: and rename them with this table.

RemovedUse instead
base-tabletable
checkboxcheckbox-indicator
codeblock, codeblock-copy-button, codeblock-header, codeblock-titlecode-block, code-block-copy-button, code-block-header, code-block-title
date-input-clear-icon, date-range-input-clear-icon, multi-selector-clear-icon, selector-clear-iconinput-clear-icon
hovercardhover-card
naviconnav-icon
popover-surfacepopover
progressbar, progressbar-fill, progressbar-mark, progressbar-trackprogress-bar, progress-bar-fill, progress-bar-mark, progress-bar-track
radio, radio-dotradio-indicator, radio-indicator-dot
statusdotstatus-dot
textareatext-area

The old names are still well-formed, so they do not throw, but no component uses them any more, so they have no effect. Full detail in Extend a theme.

Components no longer emit bare primary or sm classes ​

What changed: components such as Button no longer put .primary, .secondary, .ghost, .destructive, .sm, .md or .lg on their element. That information now lives in [data-variant] and [data-size] (upstream #6121).

Before:

css
.my-toolbar .astryx-button.primary {
  border-radius: 999px;
}

Now:

css
.my-toolbar .astryx-button[data-variant='primary'] {
  border-radius: 999px;
}

What to do: look for "component class plus variant word" in your stylesheets and switch to the attribute selector ([data-variant], [data-size]).

Component APIs ​

labelHidden is now isLabelHidden ​

What changed: the property that hides a field label is isLabelHidden. The old labelHidden still works and goes away in a later version.

Before:

vue
<TextInput v-model="name" label="Name" labelHidden />

Now:

vue
<TextInput v-model="name" label="Name" is-label-hidden />

What to do: replace label-hidden with is-label-hidden on Typeahead, Tokenizer, MultiSelector, Token, PowerSearch, DateInput, DateRangeInput, DateTimeInput and TimeInput.

PowerSearch hides its label by default, and its rule is slightly different: when both names are passed the new one wins, when only the old one is passed the old one wins, and when neither is passed the label stays hidden. Every other component hides the label when either name is true.

Selector variant="default" is now variant="input" ​

What changed: the bordered input step is called input. default is the old name and still works.

Before:

vue
<Selector v-model="fruit" :options="options" variant="default" />

Now:

vue
<Selector v-model="fruit" :options="options" variant="input" />

What to do: replace variant="default" with variant="input". Leaving variant out behaves as before, still a bordered input. default keeps working for now and goes away in a later version.

MultiSelector and ComplexSelector only have input, secondary and ghost, so they need no change.

Four new styling properties ​

What changed: input components gained containerClass, containerStyle, controlClass and controlStyle (upstream #7336 #7339 #7337 #7341).

An input has two places you can style:

  • The shell: the layer that holds the label, the control and the status message. Use container*.
  • The control box: the box that draws the border and background and holds icons and the clear button. Use control*.

Components with them: TextInput, TextArea, NumberInput, FileInput, DateInput, DateRangeInput, DateTimeInput, TimeInput, Typeahead, Tokenizer, Selector, MultiSelector, ComplexSelector, CheckboxList, RadioGroup, RadioList, Slider, PowerSearch.

Before: you could only write class on the component root, and where it landed depended on the component.

vue
<TextInput v-model="name" label="Name" class="mt-4" />

Now: use the new properties to target the shell or the control box.

vue
<TextInput
  v-model="name"
  label="Name"
  container-class="mt-4"
  control-class="font-mono"
/>

What to do: nothing is required. A class or style written on the root keeps today's landing spot; it is not dropped and you get no warning. This differs from upstream, which drops it and points you at control*. Switch to container* or control* only when a style lands somewhere you did not want.

Two things to know:

  • container* only applies when there is a shell. Inside InputGroup, or inside a FormLayout with a sideways label, there is no shell, so it does nothing and a development warning appears once.
  • control* never lands on the native input.

useDismiss takes dismissOnEscape instead of enableEscape ​

What changed: the option used to ask "should Escape be handled". It now asks "what should Escape do when it reaches this layer" (upstream #4881).

Before:

ts
useDismiss({
  container,
  isEnabled,
  enableEscape: true,
  onDismiss: close,
})

Now:

ts
useDismiss({
  container,
  isEnabled,
  dismissOnEscape: 'close',
  onDismiss: close,
})

Three values:

  • 'close': close this layer. This is the default.
  • 'block': swallow the key and stay open.
  • 'ignore': leave the key alone, for an input that handles Escape itself.

What to do: true becomes nothing or 'close'. false needs a choice: 'block' stops Escape at this layer, 'ignore' lets the key travel on.

Behavior ​

Tab and TabMenu must sit inside TabList, Step must sit inside Stepper ​

What changed: a Tab outside a TabList used to degrade silently. It now throws. The same happens when an overlay boundary resets the membership (upstream #6457).

Before: a Tab rendered without a TabList still appeared, only keyboard handling was wrong.

Now: you get useTabListContext must be used within TabList. A Step outside a Stepper gets useStepperContext must be used within Stepper.

What to do: wrap each member in its container. If you use one inside overlay content, put another local TabList or Stepper inside the overlay. The overlay is a new membership boundary, so the outer container does not count.

useAstryxLocale() can return a tag with a region ​

What changed: locale tags keep their region now. en-gb becomes en-GB, ja-jp becomes ja-JP, and dates and numbers follow that region.

zh, zh-CN and zh-Hans still become zh-CN. A tag that cannot be parsed falls back to en.

Before: useAstryxLocale() always returned a short tag such as en or zh-CN.

Now:

ts
const locale = useAstryxLocale()
locale.value === 'en' // false when the tag is en-GB

What to do: do not compare the whole tag. Check the start of it.

ts
const locale = useAstryxLocale()
locale.value.startsWith('zh') // Chinese

What changed: NavHeadingMenu and NavHeadingMenuItem used to ignore id, aria-*, data-* and event listeners written on the root. These now land on the menu element (upstream #7331).

What to do: nothing. But if you worked around the old behavior with an extra wrapper or a DOM query, that workaround may now run twice. Check it and remove it.

Text and language ​

Default names follow the current language ​

What changed: default accessible names that you never passed as props now follow the current language. This covers dropdown menus, context menus, the more-options button, the dialog close button, banner expand and collapse, and the switch loading label (upstream #6125 #7289).

Before: screen readers always heard English: Menu, Context menu, More options, Close, Expand, Collapse, Loading.

Now: a Chinese page gets 菜单, 快捷菜单, 更多选项, 关闭, 展开, 收起, 加载中. An English page still gets the English sentences.

What to do: if a test or script depends on those exact English strings, pass them yourself: label, closeButtonLabel, loadingLabel, expandLabel, collapseLabel.

Built-in English text now matches upstream word for word ​

What changed: the built-in English text now matches upstream exactly. The Chinese text was adjusted to match as well.

These are the visible changes:

ComponentBeforeNow
SideNavSidebarSide navigation
AppShell mobile nav and mobile drawerNavigationMobile navigation
MobileNav close buttonCloseClose navigation
Selector and MultiSelector with no optionsNo matchesNo options
Selector and MultiSelector read-onlyRead-onlyRead only
PowerSearch number equals / not equalsequals / does not equalis / is not
Typeahead clear buttonClearClear selection
Typeahead and Tokenizer loadingLoading resultsLoading
DateInput unavailable dateThat date is not availableInvalid date
Pagination first and last buttonsFirst page / Last pageGo to first page / Go to last page
Stepper status wordsCompleted / Warning / Errorcompleted / warning / error

FileInput's three error messages and Calendar's three range announcements changed too, both to the upstream sentences.

What to do: update your own assertions. If your product supplies this text, nothing moves.

Numbers at a thousand and above get a separator ​

What changed: numbers inside built-in text use the separator of the current language, so 1234 becomes 1,234.

This covers Pagination page numbers and totals, Selector and MultiSelector result counts, Carousel slide numbers, CommandPalette result counts, Lightbox positions, AvatarGroup overflow, table selection counts and Stepper step numbers.

What to do: update the places that assert this text.

Nothing to change ​

These upstream breaking changes have no counterpart in the Vue version.

  • The astryx layout command group: we have no such command.
  • astryx doctor no longer reads the agent environment variables: we have no such behavior.
  • emptySearchResultsText, minSizePx / maxSizePx on useResizable, isRtl on useGridFocus / useListFocus, and isImeKeyEvent from the hooks entry: none of these names exist in the Vue version, so writing them does nothing. Use emptySearchText, minSize / maxSize instead.