Fortal
Fortal is a prebuilt design system that ships with Remix. It provides a full set of component styles — buttons, inputs, menus, badges, and everything else Remix offers — built on top of the Radix color scales and spacing system. You can drop it into a Remix app and get a polished, modern look without defining a single token yourself.
Each Fortal widget delegates to a regular Remix fortal*Styler underneath. Those stylers use the same fluent API you’d use to write your own, so you can still extend, override, or fork any piece of the design system.
What Fortal Gives You
- A complete set of component styles — one or more Fortal styles per Remix component, fully configured for every interaction state (hover, press, focus, disabled)
- A curated token system — colors, spacing, border radii, shadows, typography, and border widths, all based on Radix’s color science and scale conventions
- Variants — most components expose multiple Fortal variants (e.g.,
solid,soft,surface,outline,ghost) so you can match the visual weight to the context - Dark mode support — Fortal’s token system resolves automatically in light and dark themes through
FortalScope
Installation
Fortal ships with Remix — no extra package to add. Import it alongside Remix:
import 'package:remix/remix.dart';Then wrap the part of your app that should use Fortal in a FortalScope. The scope provides the token values that every Fortal widget, fortal*Styler, and FortalTokens.* call resolves against:
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FortalScope(
child: Scaffold(
body: Center(
child: FortalButton.solid(
onPressed: () {},
label: 'Fortal Button',
),
),
),
),
);
}
}Without FortalScope in the widget tree above a Fortal-styled component, token lookups have no values to resolve and the component falls back to defaults.
Component Variants
Most Remix components expose generated Fortal* widgets with named constructors for fixed variants:
FortalButton.solid(label: 'Save', onPressed: () {})
FortalButton.soft(label: 'Save', onPressed: () {})
FortalButton.surface(label: 'Save', onPressed: () {})
FortalButton.outline(label: 'Save', onPressed: () {})
FortalButton.ghost(label: 'Save', onPressed: () {})Use the unnamed constructor when the variant is selected dynamically:
FortalButton(
variant: selectedVariant,
onPressed: () {},
label: 'Save',
);Call the matching fortal*Styler directly when you need a custom Remix widget composition or want to extend the preset style.
Not every component has every variant — a RemixSpinner doesn’t need a ghost variant. Refer to each component’s reference page for the full list of Fortal variants it ships.
Design Tokens
Fortal is built on a scale-based token system that mirrors Radix’s conventions:
| Token group | Scale |
|---|---|
| Colors — accent | 12 steps (accent1 → accent12) |
| Colors — gray | 12 steps (gray1 → gray12) |
| Colors — contrast | accentContrast, grayContrast for text on filled surfaces |
| Spacing | 9 steps (space1 → space9) |
| Border radius | 6 steps (radius1 → radius6) |
| Shadows | 6 levels (shadow1 → shadow6) |
| Typography | 9 sizes (text1 → text9) |
| Border widths | borderWidth1 → borderWidth4 |
Each token is a function on the FortalTokens class. Call it from any style:
final style = ButtonStyler()
.color(FortalTokens.accent9())
.paddingAll(FortalTokens.space4())
.borderRadius(.all(FortalTokens.radius3()))
.label(.color(FortalTokens.accentContrast()));The accent9 step is the “solid” color — the strongest fill suitable for primary actions. The 12-step scale maps numbered steps to specific uses:
| Steps | Use for |
|---|---|
| 1–2 | App backgrounds |
| 3–5 | Component backgrounds (hover states on higher steps) |
| 6–8 | Borders and separators |
| 9–10 | Solid fills (primary actions) |
| 11–12 | Text and high-contrast elements |
This mapping is consistent across every Radix-based design system — if you know Radix, you know Fortal.
Token functions like FortalTokens.accent9() resolve to the current theme’s value at paint time. That means a single style definition works in both light and dark mode without rewriting.
Customizing Fortal
Because every fortal*Styler returns a regular Remix style, you can chain additional methods to override or extend any piece of it:
final style = fortalButtonStyler(variant: .solid)
.borderRadius(.circular(8))
.paddingX(32)
.onHovered(.scale(1.05));The chain above starts from Fortal’s solid button, overrides the border radius and horizontal padding, and adds a scale-on-hover modifier. Every other Fortal property — colors, state styling, typography — passes through untouched.
You can also mix Fortal tokens into a completely custom style:
final style = ButtonStyler()
.color(FortalTokens.accent9())
.paddingAll(FortalTokens.space4())
.borderRadius(.all(FortalTokens.radius3()))
.onHovered(.color(FortalTokens.accent10()));This gives you Fortal’s token consistency without starting from its component styles.
When to Use Fortal
Reach for Fortal when:
- You want a ready-made look that pairs with Radix’s color science
- You need dark-mode-aware components without wiring up your own theme
- You’re prototyping and don’t yet have a design system of your own
- Your design system can be expressed as overrides on top of Fortal’s foundation
Skip Fortal when your design system has strong opinions that clash with Radix’s scale — in that case, build your own styles directly with the Mix API. Remix components work exactly the same either way.
Next Steps
- Components — every component, with its Fortal variants documented inline
- Radix Colors documentation — the color science Fortal inherits