---
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.

## Why a separate package

Fortal lives in its own package, `remix_fortal`, 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 — no token tables, no
Radix color data, no parity contract. If you want Fortal, you add one dependency
and get the whole catalog.

`remix_fortal` depends on `remix` and does not re-export it. Import both when you
need base Remix widgets alongside Fortal ones.

## Installation

```bash
flutter pub add remix_fortal
```

## 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 'package:remix_fortal/remix_fortal.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 'package:remix_fortal/remix_fortal.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.

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 'package:remix_fortal/remix_fortal.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 'package:remix_fortal/remix_fortal.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`. Add
both packages because `remix_fortal` deliberately does not re-export its
dependencies:

```bash
flutter pub add mix_chart remix_fortal
```

```dart
import 'package:mix_chart/mix_chart.dart';
import 'package:remix_fortal/remix_fortal.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

Fortal depends on and re-exports the complete 318-glyph Radix Icons 1.3.2
catalog from `remix_ui_icons`. `FortalIcons` remains a compatibility alias for
`RemixIcons`, so existing references continue to compile and Flutter can
subset the font. There is no name-to-icon map on that class.

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).
