Skip to content
Blog

Migrating to Astryx and Going Global: Migration Strategy, i18n, and Browser Support

Learn how to incrementally migrate from shadcn/Radix/Tailwind to Astryx, internationalize your app with built-in i18n, and understand browser support tiers.

Published on July 31, 2026

AI Assistant

This tutorial covers three operational concerns: moving from an existing design system to Astryx incrementally, internationalizing your app, and understanding browser support requirements.

Part 1: Incremental Migration

Astryx is designed for incremental migration — old and new systems coexist during the transition. You migrate one route or surface at a time, not a global class replacement.

The 9-Step Migration Plan

Step 1: Install and Initialize

npm install @astryxdesign/core @astryxdesign/theme-matcha @astryxdesign/cli
npx astryx init

Step 2: Wrap App Root with Theme

Wrap your entire app with the <Theme> component to get Astryx tokens working everywhere:

import { Theme } from '@astryxdesign/core/theme';
import { neutralTheme } from '@astryxdesign/theme-neutral/built';

export function AppRoot({ children }: { children: React.ReactNode }) {
  const [mode, setMode] = useState<'system' | 'light' | 'dark'>('system');
  return (
    <Theme theme={neutralTheme} mode={mode}>
      {children}
    </Theme>
  );
}

Step 3: Declare CSS Layer Order

Tailwind v4:

@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "@astryxdesign/core/reset.css";
@import "@astryxdesign/core/astryx.css";
@import "@astryxdesign/theme-matcha/theme.css";
@import "@astryxdesign/core/tailwind-theme.css";
@import "tailwindcss/utilities.css" layer(utilities);

Step 4: Foundation Smoke Test

Create a standalone check page to verify Astryx works before migrating any route:

export default function FoundationCheck() {
  const [email, setEmail] = useState('');
  return (
    <div data-foundation-check>
      <VStack gap={4}>
        <Button label="Primary action" variant="primary" />
        <TextInput label="Email" value={email} onChange={setEmail} />
        <Card>One card with default padding</Card>
      </VStack>
    </div>
  );
}

Steps 5-7: Migrate in Order

  1. Persistent frameAppShell, TopNav, SideNav
  2. Shared primitivesButton, TextInput, Switch, Selector, etc.
  3. Global workflows — command palette, settings, dialogs

Step 8: Delete Legacy Classes LAST

Only remove legacy Tailwind/shadcn/Radix classes after everything is verified.

Step 9: Verify

Check light/dark mode, keyboard navigation, responsive layout, and take screenshots.

shadcn/Radix to Astryx Component Mapping

LegacyAstryx Equivalent
<button>Button or IconButton
<input>TextInput
<textarea>TextArea
<Switch>Switch
<Checkbox>CheckboxInput
<RadioGroup>RadioList
<Select>Selector or Typeahead
<CommandDialog>CommandPalette
<DropdownMenu>DropdownMenu
<Alert>Banner or Toast
<Dialog>Dialog or AlertDialog

Part 2: Internationalization (i18n)

Astryx provides a built-in internationalization system via InternationalizationProvider and useTranslator.

Quick Start

import { InternationalizationProvider } from '@astryxdesign/core';

function App() {
  return (
    <InternationalizationProvider locale="en">
      <YourApp />
    </InternationalizationProvider>
  );
}

Read translated strings with the useTranslator hook:

import { useTranslator } from '@astryxdesign/core';

function SaveButton() {
  const t = useTranslator();
  return <button>{t('@myapp.actions.save')}</button>;
}

Providing Catalogs

Copy @astryxdesign/core/locales/en.json as your starting point, translate the values, then provide:

import fr from './locales/astryx/fr.json';

<InternationalizationProvider locale="fr" messages={{ fr }}>
  <App />
</InternationalizationProvider>
  • Namespace your app keys with @myapp.* to separate from Astryx internal keys (@astryx.*)
  • Untranslated keys fall back through the locale chain (e.g., pt-BRpten)
  • Partial translations are fully supported

Runtime Language Swap

No page reload needed:

const [locale, setLocale] = useState<'en' | 'fr'>('en');

<InternationalizationProvider locale={locale} messages={{ fr }}>
  <Button
    label={locale === 'en' ? 'Français' : 'English'}
    onClick={() => setLocale(l => (l === 'en' ? 'fr' : 'en'))}
  />
  <App />
</InternationalizationProvider>

Coexistence with Other i18n Libraries

Astryx’s i18n system works alongside libraries like react-intl:

<IntlProvider locale="fr" messages={appFr}>
  <InternationalizationProvider locale="fr" messages={{ fr: astryxFr }}>
    <Pricing />
  </InternationalizationProvider>
</IntlProvider>

Pseudo-Localization for Testing

Reveal hardcoded strings and layout breaks under longer text:

import pseudo from '@astryxdesign/core/locales/pseudo.json';

<InternationalizationProvider locale="pseudo" messages={{ pseudo }}>
  <App />
</InternationalizationProvider>

Part 3: Browser Support

Astryx uses a 3-tier browser support system based on Baseline, not fixed version numbers.

The 3-Tier System

TierBaselineWhat Works
Tier 1: Full fidelityCurrent (2026)Everything including CSS Anchor Positioning
Tier 2: FunctionalBaseline - 2 years (2024)Usable, no anchor positioning
Tier 3: Best-effortBelow Tier 2Won’t crash, colors may degrade

Three Modern Features That Set the Floor

FeatureRoleWidely Available
CSS Anchor PositioningPositions tooltips, menus, popovers relative to triggerBaseline 2026
Popover APIOpens/closes layered surfaces with light-dismissBaseline 2025
CSS light-dark()Auto color mode switching via CSSMid-2024

Feature Detection

const hasPopover = typeof HTMLElement !== 'undefined'
  && typeof HTMLElement.prototype.showPopover === 'function';

const hasAnchorPositioning = CSS.supports('anchor-name', '--x');

const hasLightDark = CSS.supports('color', 'light-dark(#000, #fff)');

Astryx Guarantees

  • Components never throw on missing platform APIs
  • Tier 1 and Tier 2 are officially supported and tested
  • Always feature-detect rather than hardcoding version numbers