---
title: Fortal
description: A Radix Themes-inspired preset theme and widget catalog for Remix
---

Remix gives you complete freedom to build any design system, which also means it
ships no theme of its own. **Fortal** is the ready-made alternative: a
comprehensive set of prebuilt styles based on
[Radix Themes 3.3.0](https://www.radix-ui.com/themes), providing a polished,
modern UI out of the box while remaining fully customizable.

![A selection of Fortal components and variants](/assets/fortal-catalog.png)

This overview covers installation and theming. Use the [Catalog](/fortal/catalog)
for widget sizes, variants, and defaults, or [Typography](/fortal/typography)
for text styles. Live examples and their source live on the individual
[component pages](/components/button).

## Why a separate preset

Fortal lives in its own application-owned preset so that `remix` stays a
genuinely theme-free component library. If you are building your own design
system on Remix, you pay nothing for a theme you never use. If you choose
Fortal, the CLI copies its token tables, Radix color data, and recipes into your
application, where they become your source.

The repository keeps `registry_source` (`lib/src/fortal`) as the analyzed authoring and
Radix parity surface. Consumer applications do not depend on that package.

## Installation

The commands below apply after the first CLI release and Remix beta.9.
For checkout use, follow the [Open Code installation instructions](/open-code#install).

```bash
flutter pub add dev:remix_cli
dart run remix_cli:remix init --prefix Fortal --preset fortal
dart run remix_cli:remix add button
```

The `Fortal` prefix keeps the API names shown throughout this guide. Add each
additional item when you use it, and import your installed `lib/ui/ui.dart`
barrel from application code.

## Quick Start

Wrap your app with `FortalScope` to provide the design tokens, then use the
`Fortal*` widgets. Named constructors select a fixed variant; use the unnamed
constructor with `variant:` when the choice is dynamic:

```dart
import 'package:flutter/material.dart';
import 'ui/ui.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FortalScope(
      child: WidgetsApp(
        color: Colors.white,
        builder: (_, _) => Center(
          child: FortalButton.solid(
            onPressed: () {},
            label: 'Fortal Button',
          ),
        ),
      ),
    );
  }
}
```

`FortalScope` is a `MixScope`, so it satisfies the theme-scope row of Remix's
[host capabilities](/#host-capabilities) table. Place it above your app widget so
that overlay and route content inherits the tokens.

## Scope placement

The **outermost** `FortalScope` provides the design tokens and establishes a
courtesy `DefaultTextStyle` for bare Flutter `Text`: the Radix theme root run —
`text3` (16px, 1.5 line height, 0 letter spacing) at `gray-12`, regular weight,
with no pinned font family. This is analogous to Material's `bodyMedium`; it is
not the source of a Fortal typography run. Unsized `FortalText`, `FortalCode`,
`FortalKbd`, and `FortalLink` resolve their documented defaults directly from
the active scope's tokens.

A **nested** scope re-scopes tokens without restating the courtesy bare-`Text`
run. Fortal typography re-resolves against those nested tokens, so gray or
scaling changes apply to the subtree, while ordinary Flutter text keeps its
nearest inherited style.

Where the outermost scope goes therefore depends on your host:

| Host | Put `FortalScope` |
|------|-------------------|
| `WidgetsApp`, a router, or a custom host | Above the app, as in the Quick Start above |
| `MaterialApp` or `CupertinoApp` | Inside `builder:` |

```dart
import 'package:flutter/material.dart';
import 'ui/ui.dart';

class MyMaterialApp extends StatelessWidget {
  const MyMaterialApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) => FortalScope(child: child!),
      home: const Center(child: FortalText('Themed')),
    );
  }
}
```

`MaterialApp` and `CupertinoApp` hand `WidgetsApp` their own root
`DefaultTextStyle`, and `WidgetsApp` installs it below everything that wraps the
app. A scope placed above `MaterialApp` still supplies tokens, but its root text
run is overridden. `builder:` sits below that style and above the `Navigator`,
so pushed routes and raw `Overlay` entries receive the Fortal fallback.

A [`RemixToastScope`](/components/toast) goes the other side of the
`Navigator`: in `home` or a persistent router shell, below the Overlay it
renders into. Its toasts then inherit this scope's tokens live, like any other
descendant.

The root run is only a fallback for bare Flutter `Text`, and it follows
Flutter's normal text-style cascade. A nearer `DefaultTextStyle`, including one
installed by `Material` or `Scaffold`, wins for that bare text. Fortal
typography does not inherit it: omitted sizes use the `text3` token, and an
explicit `size:` selects another token size. Transparent, non-accent
`FortalCode.ghost` is the deliberate exception for foreground only, so it can
blend into surrounding text without inheriting that run's metrics.

<Warning>
  If bare Flutter `Text` inside a hand-rolled `OverlayEntry` renders red and
  monospace with a yellow double underline, the scope is above `MaterialApp`
  rather than inside `builder:`. That is Flutter's "put your text in a
  Material" fallback style; Fortal typography pins its own token run.
</Warning>

## Customizing Fortal Styles

Fortal widgets call the matching `fortal*Style` recipe internally. Use those
recipes directly when you need a custom Remix widget composition:

```dart
import 'package:flutter/material.dart';
import 'ui/ui.dart';

final style = fortalButtonStyle(variant: FortalButtonVariant.solid)
  .borderRadius(.all(const Radius.circular(8)))
  .padding(.horizontal(32))
  .onHovered(.scale(1.05));
```

## Fortal Design Tokens

Fortal styles are built on a robust token system that includes:

- **Colors**: 12-step accent and gray scales (powered by Radix Colors)
- **Spacing**: 9-step spacing scale
- **Border Radius**: 6-step radius scale
- **Shadows**: 6-level shadow system
- **Typography**: 9-size type scale
- **Border Widths**: Consistent stroke weights

You can use these tokens directly in your custom styles:

```dart
import 'package:remix/remix.dart';
import 'ui/ui.dart';

final style = ButtonStyler()
  .color(FortalTokens.accent9())
  .padding(.all(FortalTokens.space4()))
  .borderRadius(.all(FortalTokens.radius3()))
  .label(TextStyler().color(FortalTokens.accentContrast()));
```

## Typography

Fortal adds five typography families that base Remix has no counterpart for:
`FortalText`, `FortalHeading`, `FortalCode`, `FortalKbd`, and `FortalLink`. They
share one nine-step `FortalTextSize` scale and one `FortalTextWeight` enum. See
[Typography](/fortal/typography) for widget selection, controls, semantics,
recipe customization, and the documented parity boundaries.

## Charts

Fortal provides themed line, bar, and pie recipes backed by `mix_chart`. Install
the owned chart recipe through the same configured preset:

```bash
dart run remix_cli:remix add chart
```

```dart
import 'package:mix_chart/mix_chart.dart';
import 'ui/ui.dart';

final chart = FortalLineChart(
  semanticsLabel: 'Weekly revenue',
  series: [
    LineSeries(
      id: 'revenue',
      label: 'Revenue',
      points: [
        ChartPoint(id: 'mon', x: 0, y: 18),
        ChartPoint(id: 'tue', x: 1, y: 31),
      ],
    ),
  ],
);
```

Use `FortalLineChart`, `FortalBarChart`, and `FortalPieChart` for the ready-made
widgets. Their matching `fortal*ChartStyle()` recipes can style the underlying
`mix_chart` widgets directly.

## Icons

Install the optional application-owned icon aliases through the configured
preset:

```bash
dart run remix_cli:remix add icons
```

That item adds `remix_ui_icons` and creates `FortalIcons` with the small alias
set used by your UI layer. Add, rename, or remove those aliases as the
application vocabulary evolves. Import
`package:remix_ui_icons/remix_ui_icons.dart` and use `RemixIcons` directly when
you need the complete 318-glyph Radix Icons 1.3.2 catalog.

Catalogs, galleries, and drift tests that must enumerate every glyph can import
the opt-in index. An application that never imports it keeps full font
subsetting:

```dart
import 'package:remix_ui_icons/icons_index.dart';

final icon = remixIconsByName['check'];
```

## Soft variants and contrast

Default `fortalBadgeStyle(variant: .soft)` pairs fill `accentA3` with label
`accentA11`. Step 11 is Radix **low-contrast text**, not a WCAG AA 4.5:1
guarantee against that fill composited over `colorPanelSolid`. In light mode
some of the 31 accents miss 4.5:1 on that composite. Dark-mode default and
`highContrast: true` (label `accent12`) pass AA. Use `highContrast: true` when
light-mode soft labels must meet WCAG AA.

## Covered components

Every Remix component has a matching Fortal recipe and preset widget. The
[Catalog](/fortal/catalog) lists every widget with its sizes, variants,
defaults, named constructors, and documented parity boundaries in one place; it
is generated from the same pinned Radix contract that CI holds against the
recipes, so it cannot drift from the code. For usage examples, see the
`## Fortal widgets` section on each [component page](/components/button).
