Using Remix
Build accessible Flutter interfaces with Remix behavior and either custom
styles or Fortal's ready-made Radix-inspired theme. For a new standalone
design-system package built on Remix, use building-remix-design-system.
Choose the source layer
Inspect the project's pubspec.yaml and remix.yaml before assuming how Remix
is configured.
| Need | Source and API |
|---|---|
| Accessible component behavior with a custom visual system | remix; use Remix* widgets and *Stylers |
| Ready-made Radix-inspired visuals | remix_cli with preset: fortal; use the configured scope and prefixed widgets |
| Fortal tokens with a customized composition | installed Fortal source plus remix; apply the prefixed recipe to a Remix* widget |
| Agent-run surfaces (composer, transcript, permission, plan) | remix_agent; use Agent* widgets. It depends on remix only, has no registry item, and takes its appearance from the application's installed recipes. |
| A visual system unrelated to Fortal | base Remix styling; do not initialize the Fortal preset |
Remix ships no theme. Fortal is optional application-owned source installed by
the CLI. The repository's remix_fortal workspace package is its analyzed
authoring and parity surface, not a dependency for new consumer applications.
Preserve an existing legacy remix_fortal dependency unless the user asks to
migrate it; do not add that dependency to a new consumer.
Set up dependencies and imports
For base Remix:
flutter pub add remix
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
For a new Fortal consumer, initialize the preset and add the needed items:
This checkout prepares the first CLI release and requires Remix beta.9.
Until both releases are available, use the checkout setup in
packages/remix_cli/README.md. Use the hosted commands below after publication.
flutter pub add dev:remix_cli
dart run remix_cli:remix init --prefix Fortal --preset fortal
dart run remix_cli:remix add button
import 'package:flutter/widgets.dart';
import 'ui/ui.dart';
The examples in this skill use --prefix Fortal; honor the prefix already
recorded in remix.yaml for an initialized project. Add each recipe before
using it. Also import package:remix/remix.dart when the file uses Remix*
widgets, *Styler types, or Remix data classes that the owned barrel does not
export.
Place FortalScope correctly
Every subtree that renders a Fortal* widget, a fortal*Style() recipe, or a
FortalTokens value needs FortalScope. The outermost scope also establishes
a courtesy DefaultTextStyle for bare Flutter Text: the Radix theme root run
of text3 at gray-12, regular weight, and no pinned font family. This is a
fallback for ordinary Flutter text, analogous to Material's bodyMedium; it
does not supply the text run for Fortal typography, which resolves its own
token defaults. Transparent, non-accent FortalCode.ghost deliberately keeps
only the ambient foreground so inline code can blend with surrounding text.
Placement depends on the host, and getting it wrong costs that courtesy
bare-Text fallback:
WidgetsApp or a custom host — put the scope above the app.
FortalScope(
accent: FortalAccentColor.indigo,
gray: FortalGrayColor.slate,
brightness: Brightness.light,
child: WidgetsApp(
color: const Color(0xFFFFFFFF),
builder: (_, _) => const MyScreen(),
),
)
MaterialApp or CupertinoApp — put the scope in builder.
MaterialApp(
builder: (context, child) => FortalScope(
accent: FortalAccentColor.indigo,
child: child!,
),
home: const MyScreen(),
)
Those apps hand WidgetsApp their own root DefaultTextStyle, which is
installed below anything wrapping the app — so a scope placed above
MaterialApp still supplies tokens but loses the courtesy text fallback.
builder wraps the whole Navigator, so bare Text in pushed routes and raw
Overlay entries receives that fallback.
Symptom of the wrong placement under MaterialApp: bare Flutter Text in
a hand-rolled OverlayEntry renders red, monospace, with a yellow double
underline — that is Flutter's "put your text in a Material" fallback, not a
Remix bug. Fortal typography pins its own token run; non-accent ghost Code only
retains the ambient foreground.
A nested FortalScope re-scopes tokens only; it does not restate the courtesy
bare-Text run. Re-scoping a subtree for a different accent or scaling leaves
the surrounding Flutter text inheritance unchanged while Fortal typography
resolves against the nested tokens.
Ordinary Remix* widgets with fully custom styles do not need FortalScope.
Provide only the host capabilities in use
Remix composes inside the caller's host. Do not invent RemixApp,
RemixScaffold, or RemixOverlayHost wrappers.
| UI | Caller must provide |
|---|---|
| Ordinary widgets | Normal inherited Flutter services for that subtree |
| Fortal widgets or recipes | FortalScope plus normal Flutter services |
| Menu, select, popover, tooltip | An Overlay; use Overlay.wrap when no navigator is needed |
showRemixDialog or showRemixAlertDialog |
A caller-owned Navigator |
MaterialApp, CupertinoApp, WidgetsApp, and router-based hosts are all
valid. A host with routing commonly provides both a navigator and its overlay.
For a portal-only subtree:
FortalScope(
child: WidgetsApp(
color: const Color(0xFFFFFFFF),
builder: (_, _) => Overlay.wrap(
child: FortalMenu<String>.soft(
trigger: const RemixMenuTrigger(label: 'Actions'),
items: const [RemixMenuItem(value: 'share', label: 'Share')],
),
),
),
)
RemixMenuTrigger is a configuration object, not a widget. The same object
works with FortalMenu. For richer visual content, use
RemixMenuTrigger.builder and return non-interactive content — never a nested
button:
FortalMenu<String>(
trigger: RemixMenuTrigger.builder(
label: 'Account menu',
builder: (context, state, defaultTrigger) =>
const FortalAvatar(label: 'LF'),
),
items: const [RemixMenuItem(value: 'profile', label: 'View profile')],
)
Choose a styling path
- Prefer a
Fortal*preset for standard Fortal UI. Use a named constructor such asFortalButton.soft(...)when the variant is fixed. - Use the unnamed Fortal constructor with
variant:only when the variant is selected at runtime. - Start from
fortal*Style()and pass the result to aRemix*widget when Fortal is the baseline but the composition or styling needs overrides. - Build a
*Stylerfrom scratch when the design should not use Fortal.
Base Remix example:
final submitStyle = ButtonStyler()
.color(const Color(0xFF3E63DD))
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.circular(6))
.labelColor(const Color(0xFFFFFFFF))
.onHovered(ButtonStyler().color(const Color(0xFF3358D4)));
RemixButton(label: 'Submit', style: submitStyle, onPressed: submit)
Fortal recipe with an override:
RemixButton(
label: 'Save',
onPressed: save,
style: fortalButtonStyle(variant: .solid)
.padding(.horizontal(32))
.borderRadius(.circular(8)),
)
Do not infer that every component has the same variants or sizes. Check the Fortal reference for the exact family.
Preserve behavioral roots
Some coordination APIs remain base Remix even when their children use Fortal:
- Use
RemixTabsas the behavioral root withFortalTabBar,FortalTab, andFortalTabView; there is noFortalTabs. - Keep
RemixRadioGroup,RemixCheckboxGroup, andRemixAccordionGroupas roots around their Fortal-styled children. - Provide the required
RemixAccordionGroup.controller; tabs and menus can manage optional controllers.
Route to references
Read only the references needed for the task:
| Task | Reference |
|---|---|
| Pick a component category | Component index |
| Buttons, icon buttons, toggles, and toggle groups | Actions |
| Form controls, text areas, and selection | Forms |
| Cards, data lists/tables, loading, and display widgets | Data display |
| Popovers, dialogs, tooltips, menus, and host requirements | Overlays |
| Tabs, accordions, and disclosures | Navigation |
| Fluent styling, state/context variants, animation, callable styles | Styling |
| Fortal setup, presets, variants, sizes, scope, and tokens | Fortal |
| Text, headings, inline code, keyboard keys, and links | Fortal |
| Reference apps, product examples, variant matrices, and showcase audits | Reference showcases |
Verify the result
- Keep imports aligned with the selected source layer and configured prefix.
- Confirm Fortal content and route/overlay builders are below
FortalScope. - Confirm overlays and dialogs have the required caller-owned host capability.
- For showcases, verify product examples and exhaustive coverage use their respective rules instead of forcing one abstraction or contrast policy onto both.
- Run the project's formatter, analyzer, and relevant Flutter tests.