Skip to Content
DocsRemixFortal

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.

Fortal is also a regular Remix style underneath. Every Fortal*Style is built with the same fluent API you’d use to write your own — so you can extend, override, or fork any piece of it.

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*Style 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: RemixButton( onPressed: () {}, label: 'Fortal Button', style: FortalButtonStyle.solid(), ), ), ), ), ); } }

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

Each Remix component exposes a Fortal*Style class with named constructors for each variant. For example, FortalButtonStyle provides:

FortalButtonStyle.solid() // high-emphasis, filled FortalButtonStyle.soft() // medium-emphasis, tinted FortalButtonStyle.surface() // subtle, surface-colored FortalButtonStyle.outline() // bordered, transparent fill FortalButtonStyle.ghost() // minimal, text-only until hover

You choose the variant that matches the visual weight you want, then pass it to the component’s style parameter:

RemixButton( onPressed: () {}, label: 'Save', style: FortalButtonStyle.soft(), );

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 groupScale
Colors — accent12 steps (accent1accent12)
Colors — gray12 steps (gray1gray12)
Colors — contrastaccentContrast, grayContrast for text on filled surfaces
Spacing9 steps (space1space9)
Border radius6 steps (radius1radius6)
Shadows6 levels (shadow1shadow6)
Typography9 sizes (text1text9)
Border widthsborderWidth1borderWidth4

Each token is a function on the FortalTokens class. Call it from any style:

final style = RemixButtonStyle() .color(FortalTokens.accent9()) .paddingAll(FortalTokens.space4()) .borderRadiusAll(FortalTokens.radius3()) .label(TextStyler().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:

StepsUse for
1–2App backgrounds
3–5Component backgrounds (hover states on higher steps)
6–8Borders and separators
9–10Solid fills (primary actions)
11–12Text 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*Style is itself a Remix style, you can chain additional methods to override or extend any piece of it:

final style = FortalButtonStyle.solid() .borderRadiusAll(const Radius.circular(8)) .paddingX(32) .onHovered( RemixButtonStyle().wrap(WidgetModifierConfig.scale(x: 1.05, y: 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 = RemixButtonStyle() .color(FortalTokens.accent9()) .paddingAll(FortalTokens.space4()) .borderRadiusAll(FortalTokens.radius3()) .onHovered( RemixButtonStyle().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

Last updated on

This package is currently in development — see GitHub for details.