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
- Persistent frame —
AppShell,TopNav,SideNav - Shared primitives —
Button,TextInput,Switch,Selector, etc. - 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
| Legacy | Astryx 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-BR→pt→en) - 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
| Tier | Baseline | What Works |
|---|---|---|
| Tier 1: Full fidelity | Current (2026) | Everything including CSS Anchor Positioning |
| Tier 2: Functional | Baseline - 2 years (2024) | Usable, no anchor positioning |
| Tier 3: Best-effort | Below Tier 2 | Won’t crash, colors may degrade |
Three Modern Features That Set the Floor
| Feature | Role | Widely Available |
|---|---|---|
| CSS Anchor Positioning | Positions tooltips, menus, popovers relative to trigger | Baseline 2026 |
| Popover API | Opens/closes layered surfaces with light-dismiss | Baseline 2025 |
| CSS light-dark() | Auto color mode switching via CSS | Mid-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