diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index dd0e72e51..fdf9a50d3 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,16 +1,13 @@
name: Publish to pub.dev
-# Four tag-gated jobs rather than one job listing all packages: the reusable
-# publish workflow turns `packages:` into a parallel matrix with no ordering
-# knob, and `remix_fortal` depends on hosted `remix` and `remix_ui_icons`
-# versions, so a single job would race.
+# Three tag-gated jobs keep each package aligned with its configured pub.dev
+# automated-publishing tag pattern.
#
-# THE FOUR PACKAGES PUBLISH FROM DIFFERENT TAG PATTERNS. This is not a style
+# THE THREE PACKAGES PUBLISH FROM DIFFERENT TAG PATTERNS. This is not a style
# choice — it is each package's "Automated publishing" tag pattern on pub.dev,
# and the OIDC token is rejected if the pushed tag does not match:
#
# remix -> v (bare, e.g. v1.0.0-beta.5)
-# remix_fortal -> remix_fortal-v
# remix_ui_icons -> remix_ui_icons-v
# remix_cli -> remix_cli-v
#
@@ -25,45 +22,42 @@ name: Publish to pub.dev
# publishes, `remix-v` is what melos reads. Only the first triggers
# this workflow.
#
-# The split does not order the two publishes: each tag push starts its own run,
-# so `git push --tags` still races — and it can also fire this workflow from
-# whatever stale `v*` tags a clone happens to hold. Push the tags one at a
-# time, remix first, and wait for pub.dev to serve it before pushing the
-# remix_fortal tag. pub.dev does not verify that a dependency version exists,
-# so the wrong order ships an uninstallable remix_fortal rather than failing
-# the job.
-#
-# remix_cli is versioned independently of the remix/remix_fortal pair, so its
-# tag is pushed on its own schedule and orders against nothing. It depends on
-# no other package here — it ships a CLI and a registry of templates — so it
-# cannot race the jobs above.
+# remix_cli and remix_ui_icons are versioned independently from Remix.
+# The CLI consumer check requires its bundled Remix version to exist before
+# the CLI can publish. remix_fortal is not publishable; its
+# source remains in the repository as the Fortal preset's analyzed authoring
+# and parity surface.
on:
push:
tags:
- 'remix_cli-v[0-9]+.[0-9]+.[0-9]+*'
- - 'remix_fortal-v[0-9]+.[0-9]+.[0-9]+*'
- 'remix_ui_icons-v[0-9]+.[0-9]+.[0-9]+*'
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
- publish-remix:
- # Bare `v*` only. `refs/tags/remix_fortal-v...` does not start with
- # `refs/tags/v`, so the two conditions stay mutually exclusive.
- if: startsWith(github.ref, 'refs/tags/v')
- uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
- with:
- packages_folder_path: "packages"
- packages: "remix"
+ verify-cli-consumers:
+ if: startsWith(github.ref, 'refs/tags/remix_cli-v')
+ runs-on: ubuntu-latest
permissions:
- id-token: write
contents: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: '3.44.0'
+ cache: true
+ - name: Resolve the workspace
+ run: flutter pub get
+ - name: Verify both presets with hosted Remix
+ run: dart run melos run open-code:release:check
- publish-remix-fortal:
- if: startsWith(github.ref, 'refs/tags/remix_fortal-v')
+ publish-remix:
+ # Bare `v*` only; package-prefixed tags do not start with `refs/tags/v`.
+ if: startsWith(github.ref, 'refs/tags/v')
uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
with:
packages_folder_path: "packages"
- packages: "remix_fortal"
+ packages: "remix"
permissions:
id-token: write
contents: read
@@ -80,6 +74,7 @@ jobs:
publish-remix-cli:
if: startsWith(github.ref, 'refs/tags/remix_cli-v')
+ needs: verify-cli-consumers
uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
with:
packages_folder_path: "packages"
diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml
index 9cc80d73a..48fcd9c24 100644
--- a/.github/workflows/version.yml
+++ b/.github/workflows/version.yml
@@ -1,13 +1,10 @@
name: Prepare Version Bump
-# Pushes a branch that sets both published packages to one explicit version and
-# writes their changelogs from conventional commits. It never tags and never
-# publishes: `publish.yml` fires on a tag push, so the tag is pushed by hand
-# after this PR is reviewed and merged.
-#
-# The version is a single input rather than one per package. remix and
-# remix_fortal ship as a pair and must carry the identical version, so there is
-# no per-package value to disagree about.
+# Pushes a branch that sets Remix to one explicit version and writes its
+# changelog from conventional commits. It never tags and never publishes:
+# `publish.yml` fires on a tag push, so the tag is pushed by hand after this PR
+# is reviewed and merged. remix_fortal is an unpublished authoring package and
+# is deliberately outside this workflow.
#
# It pushes a branch and prints its compare link rather than opening the pull
# request itself. Opening one from a workflow needs "Allow GitHub Actions to
@@ -18,7 +15,7 @@ on:
workflow_dispatch:
inputs:
version:
- description: "Exact version for BOTH remix and remix_fortal (e.g. 1.0.0-beta.5)."
+ description: "Exact version for remix (e.g. 1.0.0-beta.9)."
required: true
permissions:
@@ -26,13 +23,13 @@ permissions:
jobs:
version:
- name: Bump versions and changelogs
+ name: Bump Remix version and changelog
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
- # melos derives each package's last release from its `-v*`
- # tag, so the full history and all tags must be present. A shallow
+ # melos derives Remix's last release from its `remix-v*` tag, so the
+ # full history and all tags must be present. A shallow
# clone makes it walk every commit and rewrite the whole changelog.
fetch-depth: 0
@@ -56,15 +53,6 @@ jobs:
version='${{ inputs.version }}'
- # One input applied to both packages, rather than a version per
- # package that a human has to keep in step. remix and remix_fortal
- # release in lockstep (tool/check_version_alignment.dart enforces it
- # in CI), and melos has no lockstep mode of its own — left to derive
- # versions from commits it would bump each package independently and
- # split the pair apart. Passing an explicit `-V` for both is what
- # keeps them identical.
- overrides="-V remix:$version -V remix_fortal:$version"
-
# No `dart pub global activate melos`. melos runs whichever version
# the workspace pins as a dev dependency, so a global install cannot
# decide the version and only prints a reassuring, wrong one into the
@@ -95,30 +83,39 @@ jobs:
# is not what makes this work: melos re-executes itself and the
# child does not inherit the redirect, so a redirected stdin alone
# still reached the prompt and threw StdinException.
- # Scope is explicit so a conventional commit touching remix_cli
- # cannot pull that independently versioned package into the lockstep
- # Remix/Fortal release branch.
+ # `-V` makes the workflow input authoritative rather than asking
+ # melos to infer a version from commits. Scope is explicit so
+ # independently versioned remix_cli cannot enter the release branch.
+ # Disable dependent constraint and version propagation too: melos
+ # otherwise rewrites and can version workspace dependents even when
+ # the initial package filter selects only Remix, which would pull the
+ # unpublished remix_fortal authoring package back into releases.
dart run melos version \
--scope=remix \
- --scope=remix_fortal \
+ --no-dependent-constraints \
+ --no-dependent-versions \
--no-git-commit-version \
--yes \
- $overrides < /dev/null
+ -V "remix:$version" < /dev/null
- # melos rewrites the `remix` constraint of every pubspec dependent,
- # which is how remix_fortal's pin follows the bump. remix_cli's
- # registry.yaml is data, not a pubspec, so melos never reaches it and
- # its `remix: ^` would keep pointing at the previous release.
+ # remix_cli's registry.yaml is data, not a pubspec, so melos never
+ # reaches it and its `remix: ^` would keep pointing at the
+ # previous release.
# That floor is what the CLI reports as the tested version, so this
# step is part of versioning, not a cleanup after it.
dart run tool/sync_registry_remix.dart
- # The pair must land aligned; verify rather than trust the inputs.
- # This also fails when the sync step above did not run.
+ # The Fortal preset copies its dependency floors from the default
+ # registry. Rebuild it immediately after the default floor moves so
+ # the two committed registry trees cannot disagree.
+ dart run tool/build_fortal_preset.dart
+
+ # Verify the released Remix version and registry floor agree. This
+ # also fails when the sync step above did not run.
dart run tool/check_version_alignment.dart
if git diff --quiet && git diff --cached --quiet; then
- echo "No packages required versioning."
+ echo "Remix did not require versioning."
echo "versioned=false" >> "$GITHUB_OUTPUT"
exit 0
fi
@@ -128,39 +125,38 @@ jobs:
git checkout -b release/version-packages
git add -A
- git commit -m "chore(release): version packages"
+ git commit -m "chore(release): version Remix"
git push --set-upstream origin release/version-packages --force
- name: Summarise
if: steps.bump.outputs.versioned == 'true'
run: |
url="${{ github.server_url }}/${{ github.repository }}/compare/main...release/version-packages?expand=1"
+ # GitHub expands these workflow expressions before the shell sees
+ # the intentionally single-quoted Markdown commands below.
+ # shellcheck disable=SC2016
{
- echo "### Versions bumped"
+ echo "### Remix version bumped"
echo
echo "[Open the pull request]($url)"
echo
- echo "Both packages are on \`${{ inputs.version }}\`."
+ echo "Remix is on \`${{ inputs.version }}\`."
echo
- echo "After merging, push these tags ONE AT A TIME, in this order."
+ echo "After merging, push these Remix tags one at a time."
echo "Do not \`git push --tags\`: stale \`v*\` tags in this repo"
echo "would start publish runs from the wrong commits."
echo
- echo '1. `v${{ inputs.version }}` — publishes **remix**. Wait until'
- echo ' pub.dev actually serves it before step 2; pub.dev does not'
- echo ' verify a dependency exists, so publishing remix_fortal'
- echo ' early ships an uninstallable package rather than failing.'
- echo '2. `remix_fortal-v${{ inputs.version }}` — publishes **remix_fortal**.'
- echo '3. `remix-v${{ inputs.version }}` — publishes nothing. melos'
+ echo '1. `v${{ inputs.version }}` — publishes **remix**.'
+ echo '2. `remix-v${{ inputs.version }}` — publishes nothing. melos'
echo ' reads it to derive the next release changelog.'
echo
echo "### CI on this branch"
echo
- echo 'This branch also moves the `remix` floor in the'
- echo '`remix_cli` registry. `open-code:check` resolves that floor'
- echo 'from pub.dev, so it fails until step 1 above is served.'
- echo 'Expect a red run before the tag and a green one after; do'
- echo 'not lower the floor to make it pass.'
+ echo 'CI installs both presets against checkout Remix.'
+ echo 'After pub.dev serves step 1, run'
+ echo '`dart run melos run open-code:release:check`.'
+ echo 'The CLI publish workflow requires this hosted check.'
+ echo 'Do not lower the registry floor to bypass a missing release.'
echo
echo "### remix_cli"
echo
diff --git a/README.md b/README.md
index 2aeedbe9a..48f449fd8 100644
--- a/README.md
+++ b/README.md
@@ -217,10 +217,17 @@ final destructiveButton = baseButtonStyle
## Theming with Fortal
Remix ships no theme of its own. If you want a polished, Radix Themes-inspired
-starting point instead of authoring every style yourself, add the companion
-[`remix_fortal`](https://pub.dev/packages/remix_fortal) package — it provides
-`FortalScope`, a token system, the `fortal*Style()` recipes, and a matching
-catalog of ready-made `Fortal*` widgets.
+starting point instead of authoring every style yourself, initialize the
+application-owned Fortal preset:
+
+```bash
+flutter pub add dev:remix_cli
+dart run remix_cli:remix init --prefix Fortal --preset fortal
+dart run remix_cli:remix add button
+```
+
+It copies `FortalScope`, the token system, `fortal*Style()` recipes, and the
+components you add into your application.
See the [Fortal documentation](https://docs.page/btwld/remix/fortal) to get
started.
@@ -233,7 +240,7 @@ Remix composes inside your existing Flutter host. It does not require a
| UI | Caller provides | Compatible hosts |
|----|-----------------|------------------|
| Ordinary `Remix*` widgets | The inherited Flutter services used by the widget subtree | Material, Cupertino, Widgets, and router-based hosts |
-| Widgets styled by a theme package such as Fortal | That package's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
+| Widgets styled by the Fortal preset | The preset's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
| Menu, select, popover, and tooltip | An `Overlay` | Any host exposing an overlay; use `Overlay.wrap` when no `Navigator` is needed |
| `showRemixDialog` and `showRemixAlertDialog` | A `Navigator` | Any host with a caller-owned navigator |
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 2a999a25a..445671177 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -4,13 +4,13 @@
# `analysis_options.yaml`, and this file must not shadow them. It exists for
# one reason.
#
-# `open_code/fixture/` is a standalone consumer application, not a workspace
-# member (see open_code/README.md). It resolves its dependencies from pub.dev
-# inside the throwaway app that `tool/check_open_code.dart` assembles, so it
-# has no `.dart_tool/package_config.json` here and its `package:` imports
-# cannot resolve in this checkout. Its own pubspec makes it a nested analysis
-# context, so a root-level `dart analyze` would descend into it and report
-# every import as missing.
+# The `open_code/*fixture/` directories are standalone consumer applications,
+# not workspace members (see open_code/README.md). They resolve dependencies
+# from pub.dev inside the throwaway apps that `tool/check_open_code.dart`
+# assembles, so they have no `.dart_tool/package_config.json` here and their
+# `package:` imports cannot resolve in this checkout. Their own pubspecs make
+# them nested analysis contexts, so a root-level `dart analyze` would descend
+# into them and report every import as missing.
#
# Excluding it does not weaken coverage: the checker runs `flutter analyze`
# over that exact source in the assembled app, where its dependencies are real.
@@ -20,3 +20,4 @@
analyzer:
exclude:
- open_code/fixture/**
+ - open_code/fortal_fixture/**
diff --git a/docs/components/accordion.mdx b/docs/components/accordion.mdx
index e3ca9bb6a..c649b4ea4 100644
--- a/docs/components/accordion.mdx
+++ b/docs/components/accordion.mdx
@@ -146,13 +146,13 @@ class _AccordionExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalAccordionExample extends StatefulWidget {
const FortalAccordionExample({super.key});
@@ -200,7 +200,7 @@ class _FortalAccordionExampleState extends State {
- See the [fortalAccordionStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/accordion.dart) for all available options.
+ See the [fortalAccordionStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/accordion.dart) for all available options.
## Constructor
diff --git a/docs/components/avatar.mdx b/docs/components/avatar.mdx
index a62bcfabb..8aa3658d3 100644
--- a/docs/components/avatar.mdx
+++ b/docs/components/avatar.mdx
@@ -77,12 +77,12 @@ class AvatarExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalAvatarExample extends StatelessWidget {
const FortalAvatarExample({super.key});
@@ -111,7 +111,7 @@ class FortalAvatarExample extends StatelessWidget {
Set `fallbackLength: 2` for two-character labels so the generated widget uses
- the pinned two-initial typography. See the [fortalAvatarStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/avatar.dart) for all available options.
+ the pinned two-initial typography. See the [fortalAvatarStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/avatar.dart) for all available options.
## Constructor
diff --git a/docs/components/badge.mdx b/docs/components/badge.mdx
index 65b81be51..6068c4580 100644
--- a/docs/components/badge.mdx
+++ b/docs/components/badge.mdx
@@ -76,12 +76,12 @@ class BadgeExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalBadgeExample extends StatelessWidget {
const FortalBadgeExample({super.key});
@@ -111,7 +111,7 @@ class FortalBadgeExample extends StatelessWidget {
- See the [fortalBadgeStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/badge.dart) for all available options.
+ See the [fortalBadgeStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/badge.dart) for all available options.
diff --git a/docs/components/button.mdx b/docs/components/button.mdx
index d8a1248f9..62c9f0d39 100644
--- a/docs/components/button.mdx
+++ b/docs/components/button.mdx
@@ -101,12 +101,12 @@ class ButtonExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalButtonExample extends StatelessWidget {
const FortalButtonExample({super.key});
@@ -149,7 +149,7 @@ class FortalButtonExample extends StatelessWidget {
- See the [fortalButtonStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/button.dart) for all available options.
+ See the [fortalButtonStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/button.dart) for all available options.
## Constructor
diff --git a/docs/components/callout.mdx b/docs/components/callout.mdx
index cd74b219d..3a47617db 100644
--- a/docs/components/callout.mdx
+++ b/docs/components/callout.mdx
@@ -59,12 +59,12 @@ class CalloutExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalCalloutExample extends StatelessWidget {
const FortalCalloutExample({super.key});
@@ -94,7 +94,7 @@ class FortalCalloutExample extends StatelessWidget {
- See the [fortalCalloutStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/callout.dart) for all available options.
+ See the [fortalCalloutStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/callout.dart) for all available options.
## Constructor
diff --git a/docs/components/card.mdx b/docs/components/card.mdx
index 11ae9bc3f..13881344e 100644
--- a/docs/components/card.mdx
+++ b/docs/components/card.mdx
@@ -48,12 +48,12 @@ class CardExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalCardExample extends StatelessWidget {
const FortalCardExample({super.key});
@@ -74,7 +74,7 @@ class FortalCardExample extends StatelessWidget {
- See the [fortalCardStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/card.dart) for all available options.
+ See the [fortalCardStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/card.dart) for all available options.
## Constructor
diff --git a/docs/components/checkbox.mdx b/docs/components/checkbox.mdx
index 13f7109e1..39b6a2ea0 100644
--- a/docs/components/checkbox.mdx
+++ b/docs/components/checkbox.mdx
@@ -62,12 +62,12 @@ class _CheckboxExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalCheckboxExample extends StatefulWidget {
const FortalCheckboxExample({super.key});
@@ -102,7 +102,7 @@ class _FortalCheckboxExampleState extends State {
- See the [fortalCheckboxStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/checkbox.dart) for all available options.
+ See the [fortalCheckboxStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/checkbox.dart) for all available options.
## Constructor
diff --git a/docs/components/checkbox_group.mdx b/docs/components/checkbox_group.mdx
index e6c5cf2f6..6a0f9e661 100644
--- a/docs/components/checkbox_group.mdx
+++ b/docs/components/checkbox_group.mdx
@@ -263,7 +263,7 @@ separately as intentionally unmapped at the root: caller-owned
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalCheckboxGroupExample extends StatefulWidget {
const FortalCheckboxGroupExample({super.key});
@@ -305,7 +305,7 @@ class _FortalCheckboxGroupExampleState
- See the [fortalCheckboxGroupItemStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/checkbox.dart) for all available options.
+ See the [fortalCheckboxGroupItemStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/checkbox.dart) for all available options.
## Controlled values
diff --git a/docs/components/data_list.mdx b/docs/components/data_list.mdx
index b5b403b0b..ceee5051c 100644
--- a/docs/components/data_list.mdx
+++ b/docs/components/data_list.mdx
@@ -59,7 +59,7 @@ minimum, and normal/high-contrast label roles.
```dart
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
const accountDetails = FortalDataList(
size: FortalDataListSize.size2,
diff --git a/docs/components/data_table.mdx b/docs/components/data_table.mdx
index 3b40c6b12..7e8a94d93 100644
--- a/docs/components/data_table.mdx
+++ b/docs/components/data_table.mdx
@@ -37,7 +37,7 @@ and cell editing are out of scope.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class Person {
const Person(this.id, this.name, this.role);
@@ -90,7 +90,7 @@ reorders the rows it was given.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class SortableTableExample extends StatefulWidget {
const SortableTableExample({super.key, required this.names});
@@ -146,7 +146,7 @@ scoped to the visible page and preserves selections made on other pages.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class PagedTableExample extends StatefulWidget {
const PagedTableExample({super.key, required this.allRows});
@@ -204,7 +204,7 @@ body rows while the column header and pagination footer stay in place.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class ActionCellExample extends StatelessWidget {
const ActionCellExample({super.key, required this.rows});
@@ -256,7 +256,7 @@ range without a Material host. `start` and `end` alignments follow
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class LocalizedTableExample extends StatelessWidget {
const LocalizedTableExample({super.key, required this.rows});
diff --git a/docs/components/dialog.mdx b/docs/components/dialog.mdx
index 3a784e2bc..0d5c482ec 100644
--- a/docs/components/dialog.mdx
+++ b/docs/components/dialog.mdx
@@ -19,7 +19,7 @@ A modal dialog component for focused content and actions.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class DialogExample extends StatelessWidget {
const DialogExample({super.key});
@@ -66,7 +66,7 @@ configuration is needed.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
Future showDeleteDialog(BuildContext context) async {
await showRemixAlertDialog(
@@ -93,7 +93,7 @@ Future showDeleteDialog(BuildContext context) async {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
`FortalDialog` defaults to `FortalDialogSize.size3`,
`FortalDialogAlign.center`, fills the available width up to 600 logical pixels,
@@ -103,7 +103,7 @@ preserves safe viewport insets, and uses `modal: true`. Set
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalDialogExample extends StatelessWidget {
const FortalDialogExample({super.key});
@@ -131,7 +131,7 @@ class FortalDialogExample extends StatelessWidget {
- See the [fortalDialogStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/dialog.dart) for all available options.
+ See the [fortalDialogStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/dialog.dart) for all available options.
## Show function
diff --git a/docs/components/disclosure.mdx b/docs/components/disclosure.mdx
index 0e06bebae..f772c658d 100644
--- a/docs/components/disclosure.mdx
+++ b/docs/components/disclosure.mdx
@@ -62,7 +62,7 @@ Omit `expanded` for internal state. `defaultExpanded` only applies then.
```dart
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
final disclosure = SizedBox(
width: 320,
diff --git a/docs/components/divider.mdx b/docs/components/divider.mdx
index d223108c3..ed3e1c747 100644
--- a/docs/components/divider.mdx
+++ b/docs/components/divider.mdx
@@ -42,12 +42,12 @@ class DividerExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalDividerExample extends StatelessWidget {
const FortalDividerExample({super.key});
@@ -67,7 +67,7 @@ class FortalDividerExample extends StatelessWidget {
- See the [fortalDividerStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/divider.dart) for all available options.
+ See the [fortalDividerStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/divider.dart) for all available options.
## Constructor
diff --git a/docs/components/icon_button.mdx b/docs/components/icon_button.mdx
index 02f3dc198..52d54fbe2 100644
--- a/docs/components/icon_button.mdx
+++ b/docs/components/icon_button.mdx
@@ -76,12 +76,12 @@ class IconButtonExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalIconButtonExample extends StatelessWidget {
const FortalIconButtonExample({super.key});
@@ -125,7 +125,7 @@ class FortalIconButtonExample extends StatelessWidget {
- See the [fortalIconButtonStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/icon_button.dart) for all available options.
+ See the [fortalIconButtonStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/icon_button.dart) for all available options.
## Constructor
diff --git a/docs/components/link.mdx b/docs/components/link.mdx
index 74fb88a8d..0b45655c8 100644
--- a/docs/components/link.mdx
+++ b/docs/components/link.mdx
@@ -102,12 +102,12 @@ link with no callback.
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalLinkExample extends StatelessWidget {
const FortalLinkExample({super.key});
@@ -141,7 +141,7 @@ class FortalLinkExample extends StatelessWidget {
- See the [fortalLinkStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/link.dart) for all available options.
+ See the [fortalLinkStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/link.dart) for all available options.
## Constructor
diff --git a/docs/components/menu.mdx b/docs/components/menu.mdx
index d9c1b49e1..e514f2aae 100644
--- a/docs/components/menu.mdx
+++ b/docs/components/menu.mdx
@@ -151,7 +151,7 @@ replace it.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class AccountMenuExample extends StatelessWidget {
const AccountMenuExample({super.key});
@@ -187,7 +187,7 @@ menu item data, including another submenu.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class CompoundMenuExample extends StatefulWidget {
const CompoundMenuExample({super.key});
@@ -320,13 +320,13 @@ directional submenu chevron.
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalMenuExample extends StatelessWidget {
const FortalMenuExample({super.key});
@@ -398,7 +398,7 @@ customization, pass `fortalMenuStyle(...).merge(customStyle)` to
- See the [fortalMenuStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/menu.dart) for all available options.
+ See the [fortalMenuStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/menu.dart) for all available options.
## Constructor
diff --git a/docs/components/popover.mdx b/docs/components/popover.mdx
index b49d878a4..142a44201 100644
--- a/docs/components/popover.mdx
+++ b/docs/components/popover.mdx
@@ -16,7 +16,7 @@ user must address modal content before continuing.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class AccountPopover extends StatelessWidget {
const AccountPopover({super.key});
diff --git a/docs/components/progress.mdx b/docs/components/progress.mdx
index 15111fb04..0b52ede49 100644
--- a/docs/components/progress.mdx
+++ b/docs/components/progress.mdx
@@ -45,12 +45,12 @@ class ProgressExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalProgressExample extends StatelessWidget {
const FortalProgressExample({super.key});
@@ -74,7 +74,7 @@ class FortalProgressExample extends StatelessWidget {
- See the [fortalProgressStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/progress.dart) for all available options.
+ See the [fortalProgressStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/progress.dart) for all available options.
## Constructor
diff --git a/docs/components/radio.mdx b/docs/components/radio.mdx
index 5fe9555c4..cc6230da6 100644
--- a/docs/components/radio.mdx
+++ b/docs/components/radio.mdx
@@ -112,13 +112,13 @@ class _RadioExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalRadioExample extends StatefulWidget {
const FortalRadioExample({super.key});
@@ -166,7 +166,7 @@ class _FortalRadioExampleState extends State {
- See the [fortalRadioStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/radio.dart) for all available options.
+ See the [fortalRadioStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/radio.dart) for all available options.
## Constructor
diff --git a/docs/components/segmented_control.mdx b/docs/components/segmented_control.mdx
index 09f579c47..5a625b056 100644
--- a/docs/components/segmented_control.mdx
+++ b/docs/components/segmented_control.mdx
@@ -26,7 +26,7 @@ null, and `onChanged` only emits non-null item values.
```dart
import 'package:flutter/foundation.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
FortalSegmentedControl reportingPeriod({
required String value,
diff --git a/docs/components/select.mdx b/docs/components/select.mdx
index e2600db69..672cd3627 100644
--- a/docs/components/select.mdx
+++ b/docs/components/select.mdx
@@ -127,13 +127,13 @@ class _SelectExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalSelectExample extends StatefulWidget {
const FortalSelectExample({super.key});
@@ -184,7 +184,7 @@ class _FortalSelectExampleState extends State {
`FortalSelect` applies matching trigger, menu container, and default item styles. Set `RemixSelectItem.style` only for a row-level override.
- See the [fortalSelectStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/select.dart) for all available options.
+ See the [fortalSelectStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/select.dart) for all available options.
## Constructor
diff --git a/docs/components/sidebar.mdx b/docs/components/sidebar.mdx
index 05c68a6b4..51f0fb0fe 100644
--- a/docs/components/sidebar.mdx
+++ b/docs/components/sidebar.mdx
@@ -249,7 +249,7 @@ intentionally absent from the generated Radix parity catalog.
```dart
import 'package:flutter/foundation.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
FortalSidebar buildNavigation({
required ValueChanged onSelected,
diff --git a/docs/components/skeleton.mdx b/docs/components/skeleton.mdx
index 57e8723e8..8e42e4e37 100644
--- a/docs/components/skeleton.mdx
+++ b/docs/components/skeleton.mdx
@@ -240,7 +240,7 @@ or parent constraints.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
Widget profileSkeleton(bool loading) {
return FortalSkeleton(
diff --git a/docs/components/slider.mdx b/docs/components/slider.mdx
index 0e3b4779f..2b4e5f509 100644
--- a/docs/components/slider.mdx
+++ b/docs/components/slider.mdx
@@ -74,12 +74,12 @@ class _SliderExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalSliderExample extends StatefulWidget {
const FortalSliderExample({super.key});
@@ -112,7 +112,7 @@ class _FortalSliderExampleState extends State {
- See the [fortalSliderStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/slider.dart) for all available options.
+ See the [fortalSliderStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/slider.dart) for all available options.
## Constructor
diff --git a/docs/components/spinner.mdx b/docs/components/spinner.mdx
index 7998ca0e7..75c0c71bf 100644
--- a/docs/components/spinner.mdx
+++ b/docs/components/spinner.mdx
@@ -58,12 +58,12 @@ class SpinnerExample extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalSpinnerExample extends StatelessWidget {
const FortalSpinnerExample({super.key});
@@ -83,7 +83,7 @@ class FortalSpinnerExample extends StatelessWidget {
- See the [fortalSpinnerStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/spinner.dart) for all available options.
+ See the [fortalSpinnerStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/spinner.dart) for all available options.
## Constructor
diff --git a/docs/components/switch.mdx b/docs/components/switch.mdx
index a32a2a524..1d1af1f05 100644
--- a/docs/components/switch.mdx
+++ b/docs/components/switch.mdx
@@ -84,12 +84,12 @@ class _SwitchExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalSwitchExample extends StatefulWidget {
const FortalSwitchExample({super.key});
@@ -124,7 +124,7 @@ class _FortalSwitchExampleState extends State {
- See the [fortalSwitchStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/switch.dart) for all available options.
+ See the [fortalSwitchStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/switch.dart) for all available options.
## Constructor
diff --git a/docs/components/tabs.mdx b/docs/components/tabs.mdx
index 291621552..4a4929046 100644
--- a/docs/components/tabs.mdx
+++ b/docs/components/tabs.mdx
@@ -119,13 +119,13 @@ class _TabsExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalTabsExample extends StatefulWidget {
const FortalTabsExample({super.key});
@@ -169,7 +169,7 @@ class _FortalTabsExampleState extends State {
- See the [fortalTabStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/tabs.dart) for all available options.
+ See the [fortalTabStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/tabs.dart) for all available options.
## Constructor
diff --git a/docs/components/textarea.mdx b/docs/components/textarea.mdx
index 069040344..13aa0e0b5 100644
--- a/docs/components/textarea.mdx
+++ b/docs/components/textarea.mdx
@@ -145,7 +145,7 @@ content can still grow within parent constraints.
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalTextAreaExample extends StatelessWidget {
const FortalTextAreaExample({super.key});
diff --git a/docs/components/textfield.mdx b/docs/components/textfield.mdx
index a9b6f39a7..2cc27bcb0 100644
--- a/docs/components/textfield.mdx
+++ b/docs/components/textfield.mdx
@@ -89,12 +89,12 @@ class _TextfieldExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalTextFieldExample extends StatefulWidget {
const FortalTextFieldExample({super.key});
@@ -129,7 +129,7 @@ class _FortalTextFieldExampleState extends State {
- See the [fortalTextFieldStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/textfield.dart) for all available options.
+ See the [fortalTextFieldStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/textfield.dart) for all available options.
## Constructor
diff --git a/docs/components/toggle.mdx b/docs/components/toggle.mdx
index e2f6e39f2..ff2696342 100644
--- a/docs/components/toggle.mdx
+++ b/docs/components/toggle.mdx
@@ -64,12 +64,12 @@ class _ToggleExampleState extends State {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalToggleExample extends StatefulWidget {
const FortalToggleExample({super.key});
@@ -104,7 +104,7 @@ class _FortalToggleExampleState extends State {
- See the [fortalToggleStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/toggle.dart) for all available options.
+ See the [fortalToggleStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/toggle.dart) for all available options.
## Constructor
diff --git a/docs/components/toggle_group.mdx b/docs/components/toggle_group.mdx
index 802e0b0d1..4a017cfac 100644
--- a/docs/components/toggle_group.mdx
+++ b/docs/components/toggle_group.mdx
@@ -20,7 +20,7 @@ item is in the Tab order; arrow keys move focus without changing selection.
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class ViewToggle extends StatefulWidget {
const ViewToggle({super.key});
@@ -186,5 +186,5 @@ The recipe uses edge-to-edge items, a bordered surface container, selected
accent colors, a visible keyboard focus ring, and disabled state styling.
- See the [fortalToggleGroupStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/toggle_group.dart) for all available options.
+ See the [fortalToggleGroupStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/toggle_group.dart) for all available options.
diff --git a/docs/components/tooltip.mdx b/docs/components/tooltip.mdx
index e5540b91d..0c3379e4f 100644
--- a/docs/components/tooltip.mdx
+++ b/docs/components/tooltip.mdx
@@ -92,12 +92,12 @@ class _TriggerButton extends StatelessWidget {
## Fortal widgets
-Remix includes a Fortal-themed widget for this component:
+The application-owned Fortal preset provides a themed widget for this component:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class FortalTooltipExample extends StatelessWidget {
const FortalTooltipExample({super.key});
@@ -123,7 +123,7 @@ class FortalTooltipExample extends StatelessWidget {
- See the [fortalTooltipStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/tooltip.dart) for all available options.
+ See the [fortalTooltipStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/tooltip.dart) for all available options.
## Constructor
diff --git a/docs/fortal.mdx b/docs/fortal.mdx
index ec723c335..bd0465919 100644
--- a/docs/fortal.mdx
+++ b/docs/fortal.mdx
@@ -9,23 +9,32 @@ 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
+## Why a separate preset
-Fortal lives in its own package, `remix_fortal`, so that `remix` stays a
+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 — no token tables, no
-Radix color data, no parity contract. If you want Fortal, you add one dependency
-and get the whole catalog.
+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.
-`remix_fortal` depends on `remix` and does not re-export it. Import both when you
-need base Remix widgets alongside Fortal ones.
+The repository keeps `packages/remix_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 remix_fortal
+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
@@ -34,7 +43,7 @@ constructor with `variant:` when the choice is dynamic:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class MyApp extends StatelessWidget {
@override
@@ -82,7 +91,7 @@ Where the outermost scope goes therefore depends on your host:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class MyMaterialApp extends StatelessWidget {
const MyMaterialApp({super.key});
@@ -125,7 +134,7 @@ recipes directly when you need a custom Remix widget composition:
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
final style = fortalButtonStyle(variant: FortalButtonVariant.solid)
.borderRadius(.all(const Radius.circular(8)))
@@ -148,7 +157,7 @@ You can use these tokens directly in your custom styles:
```dart
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
final style = ButtonStyler()
.color(FortalTokens.accent9())
@@ -167,17 +176,16 @@ 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:
+Fortal provides themed line, bar, and pie recipes backed by `mix_chart`. Install
+the owned chart recipe through the same configured preset:
```bash
-flutter pub add mix_chart remix_fortal
+dart run remix_cli:remix add chart
```
```dart
import 'package:mix_chart/mix_chart.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
final chart = FortalLineChart(
semanticsLabel: 'Weekly revenue',
@@ -200,10 +208,18 @@ widgets. Their matching `fortal*ChartStyle()` recipes can style the underlying
## 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.
+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
diff --git a/docs/fortal/catalog.mdx b/docs/fortal/catalog.mdx
index db2351b69..582fedce9 100644
--- a/docs/fortal/catalog.mdx
+++ b/docs/fortal/catalog.mdx
@@ -56,7 +56,7 @@ A `—` value means the widget does not expose that axis. When values are listed
### FortalAccordion
-Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/recipes/accordion.dart`
+Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/components/accordion.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -78,7 +78,7 @@ Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src
### FortalAvatar
-Radix `Avatar` · recipe `packages/remix_fortal/lib/src/recipes/avatar.dart`
+Radix `Avatar` · recipe `packages/remix_fortal/lib/src/components/avatar.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -99,7 +99,7 @@ Radix `Avatar` · recipe `packages/remix_fortal/lib/src/recipes/avatar.dart`
### FortalBadge
-Radix `Badge` · recipe `packages/remix_fortal/lib/src/recipes/badge.dart`
+Radix `Badge` · recipe `packages/remix_fortal/lib/src/components/badge.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -120,7 +120,7 @@ Radix `Badge` · recipe `packages/remix_fortal/lib/src/recipes/badge.dart`
### FortalButton
-Radix `Button` · recipe `packages/remix_fortal/lib/src/recipes/button.dart`
+Radix `Button` · recipe `packages/remix_fortal/lib/src/components/button.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -145,7 +145,7 @@ Radix `Button` · recipe `packages/remix_fortal/lib/src/recipes/button.dart`
### FortalCallout
-Radix `Callout.Root` · recipe `packages/remix_fortal/lib/src/recipes/callout.dart`
+Radix `Callout.Root` · recipe `packages/remix_fortal/lib/src/components/callout.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -166,7 +166,7 @@ Radix `Callout.Root` · recipe `packages/remix_fortal/lib/src/recipes/callout.da
### FortalCard
-Radix `Card` · recipe `packages/remix_fortal/lib/src/recipes/card.dart`
+Radix `Card` · recipe `packages/remix_fortal/lib/src/components/card.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -189,7 +189,7 @@ Radix `Card` · recipe `packages/remix_fortal/lib/src/recipes/card.dart`
### FortalCheckbox
-Radix `Checkbox` · recipe `packages/remix_fortal/lib/src/recipes/checkbox.dart`
+Radix `Checkbox` · recipe `packages/remix_fortal/lib/src/components/checkbox.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -210,7 +210,7 @@ Radix `Checkbox` · recipe `packages/remix_fortal/lib/src/recipes/checkbox.dart`
### FortalCode
-Radix `Code` · recipe `packages/remix_fortal/lib/src/recipes/code.dart`
+Radix `Code` · recipe `packages/remix_fortal/lib/src/components/code.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -247,7 +247,7 @@ Radix `Code` · recipe `packages/remix_fortal/lib/src/recipes/code.dart`
### FortalDataList
-Radix `DataList.Root` · recipe `packages/remix_fortal/lib/src/recipes/data_list.dart`
+Radix `DataList.Root` · recipe `packages/remix_fortal/lib/src/components/data_list.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -282,7 +282,7 @@ Radix `DataList.Root` · recipe `packages/remix_fortal/lib/src/recipes/data_list
### FortalDataTable
-Radix `Table` · recipe `packages/remix_fortal/lib/src/recipes/data_table.dart`
+Radix `Table` · recipe `packages/remix_fortal/lib/src/components/data_table.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -311,7 +311,7 @@ Radix `Table` · recipe `packages/remix_fortal/lib/src/recipes/data_table.dart`
### FortalDialog
-Radix `Dialog.Content` · recipe `packages/remix_fortal/lib/src/recipes/dialog.dart`
+Radix `Dialog.Content` · recipe `packages/remix_fortal/lib/src/components/dialog.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -332,7 +332,7 @@ Radix `Dialog.Content` · recipe `packages/remix_fortal/lib/src/recipes/dialog.d
### FortalDisclosure
-Radix `Collapsible` · recipe `packages/remix_fortal/lib/src/recipes/disclosure.dart`
+Radix `Collapsible` · recipe `packages/remix_fortal/lib/src/components/disclosure.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -358,7 +358,7 @@ Radix `Collapsible` · recipe `packages/remix_fortal/lib/src/recipes/disclosure.
### FortalDivider
-Radix `Separator` · recipe `packages/remix_fortal/lib/src/recipes/divider.dart`
+Radix `Separator` · recipe `packages/remix_fortal/lib/src/components/divider.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -377,7 +377,7 @@ Radix `Separator` · recipe `packages/remix_fortal/lib/src/recipes/divider.dart`
### FortalHeading
-Radix `Heading` · recipe `packages/remix_fortal/lib/src/recipes/heading.dart`
+Radix `Heading` · recipe `packages/remix_fortal/lib/src/components/heading.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -408,7 +408,7 @@ Radix `Heading` · recipe `packages/remix_fortal/lib/src/recipes/heading.dart`
### FortalIconButton
-Radix `IconButton` · recipe `packages/remix_fortal/lib/src/recipes/icon_button.dart`
+Radix `IconButton` · recipe `packages/remix_fortal/lib/src/components/icon_button.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -429,7 +429,7 @@ Radix `IconButton` · recipe `packages/remix_fortal/lib/src/recipes/icon_button.
### FortalKbd
-Radix `Kbd` · recipe `packages/remix_fortal/lib/src/recipes/kbd.dart`
+Radix `Kbd` · recipe `packages/remix_fortal/lib/src/components/kbd.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -465,7 +465,7 @@ Radix `Kbd` · recipe `packages/remix_fortal/lib/src/recipes/kbd.dart`
### FortalLink
-Radix `Link` · recipe `packages/remix_fortal/lib/src/recipes/link.dart`
+Radix `Link` · recipe `packages/remix_fortal/lib/src/components/link.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -511,7 +511,7 @@ Radix `Link` · recipe `packages/remix_fortal/lib/src/recipes/link.dart`
### FortalMenu
-Radix `DropdownMenu` · recipe `packages/remix_fortal/lib/src/recipes/menu.dart`
+Radix `DropdownMenu` · recipe `packages/remix_fortal/lib/src/components/menu.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -540,7 +540,7 @@ Radix `DropdownMenu` · recipe `packages/remix_fortal/lib/src/recipes/menu.dart`
### FortalPopover
-Radix `Popover.Content` · recipe `packages/remix_fortal/lib/src/recipes/popover.dart`
+Radix `Popover.Content` · recipe `packages/remix_fortal/lib/src/components/popover.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -556,7 +556,7 @@ Radix `Popover.Content` · recipe `packages/remix_fortal/lib/src/recipes/popover
### FortalProgress
-Radix `Progress` · recipe `packages/remix_fortal/lib/src/recipes/progress.dart`
+Radix `Progress` · recipe `packages/remix_fortal/lib/src/components/progress.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -583,7 +583,7 @@ Radix `Progress` · recipe `packages/remix_fortal/lib/src/recipes/progress.dart`
### FortalRadio
-Radix `RadioGroup.Item` · recipe `packages/remix_fortal/lib/src/recipes/radio.dart`
+Radix `RadioGroup.Item` · recipe `packages/remix_fortal/lib/src/components/radio.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -604,7 +604,7 @@ Radix `RadioGroup.Item` · recipe `packages/remix_fortal/lib/src/recipes/radio.d
### FortalSegmentedControl
-Radix `SegmentedControl.Root` · recipe `packages/remix_fortal/lib/src/recipes/segmented_control.dart`
+Radix `SegmentedControl.Root` · recipe `packages/remix_fortal/lib/src/components/segmented_control.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -641,7 +641,7 @@ Radix `SegmentedControl.Root` · recipe `packages/remix_fortal/lib/src/recipes/s
### FortalSelect
-Radix `Select` · recipe `packages/remix_fortal/lib/src/recipes/select.dart`
+Radix `Select` · recipe `packages/remix_fortal/lib/src/components/select.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -667,7 +667,7 @@ Radix `Select` · recipe `packages/remix_fortal/lib/src/recipes/select.dart`
### FortalSkeleton
-Radix `Skeleton` · recipe `packages/remix_fortal/lib/src/recipes/skeleton.dart`
+Radix `Skeleton` · recipe `packages/remix_fortal/lib/src/components/skeleton.dart`
**Other defaults:** `loading: true`
@@ -688,7 +688,7 @@ Radix `Skeleton` · recipe `packages/remix_fortal/lib/src/recipes/skeleton.dart`
### FortalSlider
-Radix `Slider` · recipe `packages/remix_fortal/lib/src/recipes/slider.dart`
+Radix `Slider` · recipe `packages/remix_fortal/lib/src/components/slider.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -716,7 +716,7 @@ Radix `Slider` · recipe `packages/remix_fortal/lib/src/recipes/slider.dart`
### FortalSpinner
-Radix `Spinner` · recipe `packages/remix_fortal/lib/src/recipes/spinner.dart`
+Radix `Spinner` · recipe `packages/remix_fortal/lib/src/components/spinner.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -737,7 +737,7 @@ Radix `Spinner` · recipe `packages/remix_fortal/lib/src/recipes/spinner.dart`
### FortalSwitch
-Radix `Switch` · recipe `packages/remix_fortal/lib/src/recipes/switch.dart`
+Radix `Switch` · recipe `packages/remix_fortal/lib/src/components/switch.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -758,7 +758,7 @@ Radix `Switch` · recipe `packages/remix_fortal/lib/src/recipes/switch.dart`
### FortalTabBar
-Radix `Tabs` · recipe `packages/remix_fortal/lib/src/recipes/tabs.dart`
+Radix `Tabs` · recipe `packages/remix_fortal/lib/src/components/tabs.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -783,7 +783,7 @@ Radix `Tabs` · recipe `packages/remix_fortal/lib/src/recipes/tabs.dart`
### FortalText
-Radix `Text` · recipe `packages/remix_fortal/lib/src/recipes/text.dart`
+Radix `Text` · recipe `packages/remix_fortal/lib/src/components/text.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -814,7 +814,7 @@ Radix `Text` · recipe `packages/remix_fortal/lib/src/recipes/text.dart`
### FortalTextArea
-Radix `TextArea` · recipe `packages/remix_fortal/lib/src/recipes/textfield.dart`
+Radix `TextArea` · recipe `packages/remix_fortal/lib/src/components/textfield.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -848,7 +848,7 @@ Radix `TextArea` · recipe `packages/remix_fortal/lib/src/recipes/textfield.dart
### FortalTextField
-Radix `TextField.Root` · recipe `packages/remix_fortal/lib/src/recipes/textfield.dart`
+Radix `TextField.Root` · recipe `packages/remix_fortal/lib/src/components/textfield.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -869,7 +869,7 @@ Radix `TextField.Root` · recipe `packages/remix_fortal/lib/src/recipes/textfiel
### FortalToggle
-Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/recipes/toggle.dart`
+Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/components/toggle.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -890,7 +890,7 @@ Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src
### FortalToggleGroup
-Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/recipes/toggle_group.dart`
+Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src/components/toggle_group.dart`
| Parameter | Values | Default |
| --- | --- | --- |
@@ -912,7 +912,7 @@ Fortal extension (no Radix counterpart) · recipe `packages/remix_fortal/lib/src
### FortalTooltip
-Radix `Tooltip` · recipe `packages/remix_fortal/lib/src/recipes/tooltip.dart`
+Radix `Tooltip` · recipe `packages/remix_fortal/lib/src/components/tooltip.dart`
**Other defaults:** `arrow: true`, `collisionPadding: 10`, `delayMilliseconds: 200`, `disableHoverableContent: false`, `maxWidth: 360`, `sideOffset: 4`
diff --git a/docs/fortal/typography.mdx b/docs/fortal/typography.mdx
index d926c8fc8..7358d9f3f 100644
--- a/docs/fortal/typography.mdx
+++ b/docs/fortal/typography.mdx
@@ -10,9 +10,10 @@ Fortal ships five typography families — `FortalText`, `FortalHeading`,
size scale (`FortalTextSize`) and one weight enum (`FortalTextWeight`) instead
of declaring five parallel copies.
-These families live only in `remix_fortal`. There is no `RemixText` or
-`RemixHeading`: base Remix stays theme-free, and a `TextStyler` plus your own
-tokens is the equivalent there.
+These families live only in the application-owned Fortal preset. There is no
+`RemixText` or `RemixHeading`: base Remix stays theme-free, and a `TextStyler`
+plus your own tokens is the equivalent there. The examples assume the preset
+was initialized with `--prefix Fortal` as shown in the [Fortal overview](/fortal).
@@ -60,7 +61,7 @@ scaling tokens, while ordinary Flutter text keeps its nearest inherited style.
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class TypographyExample extends StatelessWidget {
const TypographyExample({super.key});
@@ -125,7 +126,7 @@ the neutral color unchanged — the same gate Radix puts behind
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class TypographyVariantsExample extends StatelessWidget {
const TypographyVariantsExample({super.key});
@@ -163,7 +164,7 @@ can be level 1 at size 6 while an empty-state title stays level 2 at size 3.
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class HeadingLevelExample extends StatelessWidget {
const HeadingLevelExample({super.key});
@@ -222,7 +223,7 @@ idle treatment.
```dart
import 'package:flutter/material.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
class LinkExample extends StatelessWidget {
const LinkExample({super.key, required this.onOpenDocs});
@@ -250,9 +251,9 @@ class LinkExample extends StatelessWidget {
`linkUrl` is assistive metadata only and is **never launched**. Navigation stays
-your responsibility inside `onPressed`, which keeps `remix_fortal` free of a URL
-launcher dependency. Passing `linkUrl` without `onPressed` asserts, because an
-announced destination with no activation is a broken promise to assistive
+your responsibility inside `onPressed`, which keeps the owned recipe free of a
+URL launcher dependency. Passing `linkUrl` without `onPressed` asserts, because
+an announced destination with no activation is a broken promise to assistive
technology.
`enabled: false` keeps the callback and still refuses focus and activation, so
@@ -269,7 +270,7 @@ Each widget calls a `fortal*Style` recipe you can use directly. `FortalText` and
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
-import 'package:remix_fortal/remix_fortal.dart';
+import 'ui/ui.dart';
/// Body text on the Fortal scale with one project-specific neutral tone.
TextStyler mutedBody() => fortalTextStyle(
diff --git a/docs/index.mdx b/docs/index.mdx
index 547f634b2..e54ad43b2 100644
--- a/docs/index.mdx
+++ b/docs/index.mdx
@@ -171,10 +171,17 @@ final destructiveButton = baseButtonStyle
## Theming with Fortal
Remix ships no theme of its own. If you want a polished, Radix Themes-inspired
-starting point instead of authoring every style yourself, add the companion
-[`remix_fortal`](https://pub.dev/packages/remix_fortal) package — it provides
-`FortalScope`, a token system, the `fortal*Style()` recipes, and a matching
-catalog of ready-made `Fortal*` widgets.
+starting point instead of authoring every style yourself, initialize the
+application-owned Fortal preset:
+
+```bash
+flutter pub add dev:remix_cli
+dart run remix_cli:remix init --prefix Fortal --preset fortal
+dart run remix_cli:remix add button
+```
+
+It copies `FortalScope`, the token system, `fortal*Style()` recipes, and the
+components you add into your application.
See the [Fortal overview](/fortal) to get started.
@@ -186,7 +193,7 @@ Remix composes inside your existing Flutter host. It does not require a
| UI | Caller provides | Compatible hosts |
|----|-----------------|------------------|
| Ordinary `Remix*` widgets | The inherited Flutter services used by the widget subtree | Material, Cupertino, Widgets, and router-based hosts |
-| Widgets styled by a theme package such as [Fortal](/fortal) | That package's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
+| Widgets styled by the [Fortal preset](/fortal) | The preset's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
| Menu, select, popover, and tooltip | An `Overlay` | Any host exposing an overlay; use `Overlay.wrap` when no `Navigator` is needed |
| `showRemixDialog` and `showRemixAlertDialog` | A `Navigator` | Any host with a caller-owned navigator |
diff --git a/docs/open-code.mdx b/docs/open-code.mdx
index 9f0f906c6..a5a83cccf 100644
--- a/docs/open-code.mdx
+++ b/docs/open-code.mdx
@@ -3,11 +3,11 @@ title: Open Code
description: Install editable Remix component source into your app with the remix CLI
---
-Fortal gives you a themed catalog you *depend on*. **Open code** gives you the
-opposite trade: `remix_cli` copies a small, readable theme and one file per
-component **into your application**, and from that moment your team owns those
-files. No wrapper, no subclass, no theme-override escape hatch — you edit the
-source.
+**Open code** copies a theme and component recipes **into your application**.
+From that moment your team owns those files: no wrapper, no subclass, and no
+package-level theme override standing between you and the source. Choose the
+small `default` preset or the full Radix Themes-inspired `fortal` preset when
+you initialize the project.
Remix keeps everything that is genuinely hard: rendering, pointer and keyboard
handling, focus management, accessibility semantics, and the loading/disabled
@@ -17,22 +17,43 @@ interaction rules. The installed file only decides what things look like.
`remix_cli` has not been published. The hosted installation commands below
apply after its first release. Until then, use the checkout installation
described in the CLI README. The CLI has its own version and release schedule.
+Fortal preset does not add a `remix_fortal` dependency.
-## When to choose open code
+## Choose a preset
-| | Fortal | Open code |
+| | `default` | `fortal` |
|---|---|---|
-| You get | A dependency | Source files in `lib/` |
-| Restyling | Token and style overrides | Edit the recipe |
-| Upgrades | `flutter pub upgrade` | You own it; rerun `add --diff` to compare |
-| Best for | Ship fast on a Radix-like system | A house design system you intend to diverge |
+| Design language | Compact neutral starting point | Radix Themes 3.3.0 parity surface |
+| Theme | 15 semantic tokens | 277 tokens, Radix color scales, scaling, radius, and panel modes |
+| Component axes | Small default vocabulary | Fortal's `classic`, `solid`, `soft`, `surface`, `outline`, and `ghost` vocabulary where applicable |
+| Best for | A house system you intend to shape | Starting from a complete Radix-like system |
-Pick open code when the design system is *yours* and you expect to keep
-changing it. Pick Fortal when you want a good catalog and no maintenance.
+Both choices install local Dart under `lib/`, use Remix for behavior, and use
+the same `add --diff` / `add --overwrite` review workflow. They cannot be mixed
+inside one initialized source tree because their tokens and variant
+vocabularies are different.
## Install
+Before the first release, add the CLI from a checkout:
+
+```bash
+dart pub add "dev:remix_cli@{path: /path/to/remix/packages/remix_cli}"
+```
+
+Until Remix beta.9 is published, add this temporary `pubspec_overrides.yaml`
+to the application. Replace the path with your checkout:
+
+```yaml
+dependency_overrides:
+ remix:
+ path: /path/to/remix/packages/remix
+```
+
+After both releases are available, remove this override and use the hosted
+installation below. Both presets require Remix `^1.0.0-beta.9`.
+
Add the CLI as a project-local dev dependency so its version is pinned in your
lockfile, not in a machine-global install:
@@ -83,18 +104,19 @@ Run "remix help " for more information about a command.
## 1. Initialize
```bash
-remix init
+remix init --preset default
```
This writes two files and nothing else:
-- `remix.yaml` — your prefix and UI path
+- `remix.yaml` — your prefix, preset, and UI path
- `lib/ui/ui.dart` — a managed barrel with an export marker block
```yaml
# remix.yaml
-schema: 1
+schema: 2
prefix: Ui
+preset: default
paths:
ui: lib/ui
```
@@ -103,13 +125,14 @@ Choose the naming and location up front, because the prefix is baked into every
identifier the CLI renders:
```bash
-remix init --prefix Acme --ui-path lib/design_system
+remix init --prefix Acme --preset fortal --ui-path lib/design_system
```
-`--prefix Acme` produces `AcmeButton`, `AcmeButtonVariant`, `AcmeThemeData`,
-and `acmeButtonStyle`. The prefix must be an UpperCamel Dart identifier whose
+`--prefix Acme` produces identifiers such as `AcmeButton` and
+`acmeButtonStyle`. The prefix must be an UpperCamel Dart identifier whose
lowercased form is not a reserved word, and the UI path must live under `lib/`.
The prefixes `Remix` and `Mix` are reserved for runtime dependencies.
+The preset defaults to `default` when omitted.
`init` is also a repair command. Run it again and it recreates whichever of the
two files is missing, telling you exactly which one it touched:
@@ -130,7 +153,7 @@ remix add button --dry-run
```
Items: theme -> button
-Dependencies: remix@^1.0.0-beta.8, mix_annotations@^2.2.0-beta.1, dev:build_runner@^2.10.1, dev:mix_generator@^2.2.0-beta.3
+Dependencies: remix@^1.0.0-beta.9, mix_annotations@^2.2.0-beta.1, dev:build_runner@^2.10.1, dev:mix_generator@^2.2.0-beta.3
theme: missing; lib/ui/theme/tokens.dart, lib/ui/theme/theme_data.dart, lib/ui/theme/theme_scope.dart
button: missing; lib/ui/components/button.dart
Exports: theme/tokens.dart, theme/theme_data.dart, theme/theme_scope.dart, components/button.dart
@@ -188,20 +211,22 @@ class SignUpButton extends StatelessWidget {
## Configuration
-`remix.yaml` is the entire configuration surface — two settings, no plugins, no
-presets, no per-component overrides:
+`remix.yaml` is the entire configuration surface — three settings, no plugins
+and no per-component overrides:
```yaml
-schema: 1
+schema: 2
prefix: Ui # type prefix for every generated identifier
+preset: default # bundled recipe tree selected at init
paths:
ui: lib/ui # where installed source lives
```
| Key | Default | Rules |
|---|---|---|
-| `schema` | `1` | Must be exactly `1`. Anything else is rejected. |
+| `schema` | `2` | New files use schema 2. Schema 1 remains readable as `preset: default`; anything else is rejected. |
| `prefix` | `Ui` | ASCII UpperCamel Dart identifier. Its lowercased form must not be a Dart reserved word. `Remix` and `Mix` are reserved for runtime dependencies. |
+| `preset` | `default` | Bundled registry selected for every later `add`. Choose `default` or `fortal` at `init`. |
| `paths.ui` | `lib/ui` | Project-relative, normalized, no traversal, must be under `lib/`. Symlinks that escape the package root are rejected. |
Unknown keys are an error rather than a warning, so a typo fails loudly instead
@@ -216,14 +241,14 @@ The prefix drives two identifier casings at once. With `prefix: Acme` you get
the type `AcmeButton` and the function `acmeButtonStyle`.
-Choose `prefix` and `paths.ui` before your first `add`. The CLI will not rename
-or move source it has already installed.
+Choose `prefix`, `preset`, and `paths.ui` before your first `add`. The CLI will
+not rename, restyle, or move source it has already installed.
Running `init` with different values against an existing `remix.yaml` is a hard
error, not a migration:
```
-FormatException: Existing remix.yaml does not match the requested prefix and UI path.
+FormatException: Existing remix.yaml does not match the requested prefix, preset, and UI path.
```
Editing `remix.yaml` by hand afterwards is worse, because installed state is
@@ -295,6 +320,56 @@ ButtonStyler _variantStyle(UiButtonVariant variant) => switch (variant) {
constructor per variant (`UiButton.primary`, `.secondary`, `.outline`,
`.ghost`, `.destructive`).
+## The Fortal preset
+
+Initialize Fortal once, then use the same item commands and ownership workflow:
+
+```bash
+remix init --prefix Acme --preset fortal
+remix add button
+```
+
+The application receives `AcmeScope`, `AcmeTokens`, `AcmeButton`, and
+`acmeButtonStyle`; no installed identifier is named Fortal. Button brings its
+shared `base_button` recipe, while the theme arrives as a complete local layer:
+
+```text
+lib/ui/
+├── ui.dart
+├── theme/
+│ ├── theme.dart
+│ ├── radix_colors.dart
+│ ├── computed.dart
+│ ├── control_styles.dart
+│ ├── tokens.dart
+│ ├── theme_data.dart
+│ ├── theme_scope.dart
+│ └── surface_frame.dart
+└── components/
+ ├── base_button.dart
+ ├── button.dart
+ └── button.g.dart
+```
+
+Place `AcmeScope` below the application host. For a routed Material app, wrap
+the child of `MaterialApp.builder` with `AcmeScope`. Routes and dialogs then
+inherit its tokens and text defaults. The scope example lives in
+`lib/ui/theme/theme_scope.dart`.
+
+These are application files, not forwarding wrappers around
+`package:remix_fortal`. The authored recipes and pinned Radix Themes 3.3.0
+color data are copied from the same source used by the repository's parity
+suite. `remix` remains the behavior and styling-engine boundary;
+`mix_annotations`, `build_runner`, and `mix_generator` support adapter
+generation. `icons` additionally installs `remix_ui_icons`, and `chart`
+installs `mix_chart`. The preset adds no direct `mix`, `naked_ui`, or
+`remix_fortal` dependency.
+
+The source is deliberately editable. Change the token map, add an accent or
+variant, or merge a one-instance styler last. Future CLI versions never merge
+over those decisions automatically: inspect `remix add --diff`, then
+use `--overwrite` only when you want the new authored file wholesale.
+
## The three levels of customization
**Edit the recipe** — change it for the whole app, permanently. It is your file.
@@ -388,7 +463,7 @@ a later `remix` that these templates were never tested against. `add` reports
that after it resolves your lockfile:
```
-Resolved remix 1.0.0; this remix_cli registry was authored against 1.0.0-beta.8. Run flutter pub upgrade remix_cli, then review with --diff.
+Resolved remix 1.0.0; this remix_cli registry was authored against 1.0.0-beta.9. Run flutter pub upgrade remix_cli, then review with --diff.
```
It is a notice, not a failure — the install completes. A `remix` *below* the
@@ -451,7 +526,7 @@ No source was written.
Declaring the same package in both sections is rejected the same way. Fix your
manifest and rerun — the CLI will not edit it for you.
-## Catalog
+## Default preset catalog
The bundled registry is deliberately small, and every item after `theme`
depends on it:
@@ -499,6 +574,13 @@ and page-size control *are* those components — adding it installs all four.
| `toggle_group` | `UiToggleGroup` | variant, size |
| `tooltip` | `UiTooltip` | — |
+The `fortal` catalog contains every item above and adds `base_button`, `code`,
+`heading`, `kbd`, `sidebar`, `text`, and `typography`. `base_button` and
+`typography` are shared authored recipes that arrive transitively with the
+components that use them. Fortal's component names use the configured prefix,
+so an Acme installation exposes `AcmeSidebar`, `AcmeHeading`, and
+`AcmeText` alongside its generated controls.
+
One `add` installs one item. The names above use the default `Ui` prefix;
`--prefix Acme` gives you `AcmeButton` and friends. An axis is an enum the
recipe declares, and a recipe whose axis is named `variant` also gets one
@@ -509,10 +591,10 @@ A parameter is not an axis. `UiDivider` takes an `orientation`, but it is a
plain Flutter `Axis` the recipe accepts rather than an enum of its own, so it
generates no named constructors.
-### Charts outside Fortal
+### Charts in the default preset
-`chart` is an optional extension, not a Fortal preset and not a wrapper around
-Fortal:
+The default preset's `chart` is an optional extension with its own compact
+recipe:
```bash
remix add chart
diff --git a/docs/remix-cli-guide.html b/docs/remix-cli-guide.html
index a45edc3de..12e5d4c3a 100644
--- a/docs/remix-cli-guide.html
+++ b/docs/remix-cli-guide.html
@@ -392,7 +392,7 @@
remixremix_cli 0.1.0
- remix 1.0.0-beta.8
+ remix 1.0.0-beta.9
@@ -407,7 +407,7 @@
This is the first visual step.
Step 4 supplied the source. Your widget tree decides where that source appears.
+
A fresh Flutter project can report generated-code lints. Apply the
+ generated adapter exclusion before running flutter analyze.
+ Keep authored recipes under analysis.
+
AcmeButton.primary(…) is a named constructor. The generator emits one for each
value of the variant enum, so AcmeButton.destructive(label: 'Delete')
works the same way.
@@ -1199,9 +1232,10 @@
The whole theme WIDEST
-
Add a token here and to AcmeThemeData, and the scope map exposes it. Rename one,
- and the analyzer finds every recipe that used it. Every recipe reads its corner radius from one
- token, so one value turns every control into a pill.
+
Add the token to tokens.dart, its value to AcmeThemeData, and its mapping to AcmeThemeData.tokens.
+ Include the value in constructors, copyWith, equality, and hashCode.
+ The scope reads this explicit map. A token does not enter the map automatically.
+
Recipes that read AcmeTokens.radius share that radius value. Controls with fixed or circular geometry keep their own shape.
lib/main.dart — one value changed between two builds
@@ -1216,7 +1250,7 @@
The whole theme WIDEST
After:radius token 20
-
EFFECT
Every recipe changes.
+
EFFECT
Recipes that use the radius token change.
The recipe files were untouched between these two builds. Only the theme value moved.
Light and dark are the same mechanism. Switching at runtime changes only the value passed to
@@ -1371,8 +1405,8 @@
What the recipe can generate next
This section is a design preview
-
Every other section on this page is verified against a real installation.
- This one is not built yet. It describes the intended direction. The two
+
The other sections describe implemented CLI features. Hosted commands require package publication.
+ This section is not built yet. It describes the intended direction. The two
contracts it targets already exist, so the shapes below are real; the generator that would
emit them is not.
@@ -1407,8 +1441,7 @@
The Muse A2UI catalog schema
the catalog entry a generator would emit for AcmeButton
{
"component": { "const": "AcmeButton" },
- "description": "Labeled control that dispatches an allowed action.
- Use AcmeIconButton when a compact icon-only control is clearer.",
+ "description": "A labeled control that dispatches an allowed action. Use AcmeIconButton for a compact control without visible text.",
"properties": {
"label": { "type": "string",
"description": "Visible action label." },
@@ -1596,8 +1629,9 @@
Reference
Files it writes
-
Everything below lib/ui/ is yours. The CLI touches nothing else in your app except
- the dependency lists in pubspec.yaml.
+
The application owns the installed source below lib/ui/.
+ The CLI also writes remix.yaml and updates dependency declarations in pubspec.yaml.
+ Pub updates the lockfile and package metadata. The generator writes adapters and its build cache.
File
What it holds
Generated?
@@ -1677,7 +1711,7 @@
Dependency rules
An incompatible constraint also fails preflight. The CLI never widens a constraint you chose.
When something fails
-
Three problems meet a fresh app first.
+
These checks cover common problems in a fresh app.
Your linter flags the generated adapters. A default Flutter app includes
flutter_lints, and the current generator emits explicit this. qualifiers.
@@ -1695,9 +1729,17 @@
When something fails
With that exclude, flutter analyze lib reports No issues found!.
Text field selection handles fail. An installed text field needs an
- Overlay ancestor once it takes focus. Material, Cupertino, and any routed
+ Overlay ancestor once it takes focus. MaterialApp, CupertinoApp, and any routed
WidgetsApp already provide one; a bare WidgetsApp(builder: …) does not.
+
A default dialog fills the viewport. The default recipe styles the panel.
+ Wrap AcmeDialog in Center inside the showRemixDialog builder.
+ This lets the panel's maximum width apply. The Fortal recipe supplies its own viewport alignment.
+
+
Dialog text has unexpected decoration. The dialog captures the invoking context's inherited themes.
+ Use a context below your intended DefaultTextStyle.
+ For Fortal, place AcmeScope in the application builder, below the host.
+
“Run remix init before remix add.” The current directory has no
remix.yaml. Run the CLI from the package that owns pubspec.yaml. In a
workspace, use the member package, not the workspace root.
@@ -1754,8 +1796,9 @@
remix.yaml keys
Key
Default
Rule
-
schema
1
Must be exactly 1.
+
schema
2
New files use schema 2. Legacy schema 1 remains readable and selects the default preset.
prefix
Ui
ASCII UpperCamel. Its lowercase form cannot be a Dart reserved word. Remix and Mix are reserved for runtime dependencies.
+
preset
default
Schema 2 requires default or fortal. Schema 1 has no preset key. Choose the preset at initialization.
paths.ui
lib/ui
Normalized, project-relative, and inside lib/.
@@ -2048,9 +2091,9 @@
Provenance
What produced the commands, the source, and the images on this page.
-
Every command and status line on this page was run against a fresh Flutter application created
- for this guide. Long planning output is shortened to the lines that affect your next decision, and
- every shortened block marks the elision.
+
The command examples describe CLI 0.1.0. Hosted installation requires publication; the checkout workflow works before publication.
+ The September 7, 2026 review repeated initialization and component installation in fresh applications for both presets.
+ Long planning output is shortened, and each shortened block marks the elision.
host-neutral gallery fixture that tool/check_open_code.dart installs. Both use the
same installed lib/ui/ layer.
+
The embedded renders predate the beta.9 release target. They show the default recipes, not a hosted beta.9 installation.
+ The table below describes the current checkout verification toolchain.
+
Component
Version
remix_cli
0.1.0
Flutter
3.44.0 stable, framework 559ffa3f75
Dart
3.12.0 stable
-
remix
1.0.0-beta.8, the registry snapshot version
+
remix
1.0.0-beta.9, the current registry target; verified from a checkout before publication
mix
2.2.0-beta.5
mix_annotations
2.2.0-beta.1
mix_generator
2.2.0-beta.3
@@ -2090,7 +2136,7 @@
Provenance
diff --git a/docs/styler-api.mdx b/docs/styler-api.mdx
index 873fa502c..0dfa8494e 100644
--- a/docs/styler-api.mdx
+++ b/docs/styler-api.mdx
@@ -142,8 +142,8 @@ operation without ambiguous overloads.
Component stylers are generated from `@MixableSpec`. Preset wrapper classes are
generated from `@MixWidget`, which is not Remix-internal: any package that
depends on `remix` can annotate a recipe function with it, and the generator
-emits a widget calling the target Remix constructor. The
-[`remix_fortal`](/fortal) package does exactly this to produce its `Fortal*`
+emits a widget calling the target Remix constructor. The application-owned
+[`fortal` preset](/fortal) uses exactly this mechanism to produce its prefixed
catalog. The annotations identify the target Remix constructor and which recipe
parameters become wrapper fields. Generated files remain deterministic build
artifacts and should never be edited directly.
diff --git a/open_code/CLEAN_SHEET.md b/open_code/CLEAN_SHEET.md
index 36a18c78c..444577d7f 100644
--- a/open_code/CLEAN_SHEET.md
+++ b/open_code/CLEAN_SHEET.md
@@ -127,12 +127,13 @@ The recurring costs are visible:
## What remains outside this MVP
-Fortal stays unchanged. There is no general component marketplace, remote
-registry, automatic migration, source lockfile, or public-release automation
-for `remix_cli`.
-
-The first public release has a separate gate: confirm the pub.dev package and
-publisher, add an isolated tag contract, verify bundled assets from a staged
-archive in a clean Pub cache, publish a prerelease, and repeat the flow from the
-hosted package. Until then, project-local checkout or staged-package use is the
-honest supported path.
+**Since 2026-09-04:** Fortal is available as a second application-owned preset.
+Its separate source tree, derivation contract, and publication transition are
+recorded in [Presets: Fortal as application-owned source](PRESETS.md). There is
+still no general component marketplace, remote registry, automatic migration,
+or source lockfile. The CLI now has a tag-based publish workflow with a
+hosted consumer check.
+
+The first public release still requires package bootstrap and hosted CLI
+verification. Until then, use a checkout or staged package with checkout Remix.
+The [release instructions](RELEASING.md) define the order and required checks.
diff --git a/open_code/PRESETS.md b/open_code/PRESETS.md
new file mode 100644
index 000000000..757d3fa95
--- /dev/null
+++ b/open_code/PRESETS.md
@@ -0,0 +1,419 @@
+# Presets: Fortal as application-owned source
+
+**Status:** Implemented; publication pending
+**Base branch:** `feat/open-code`
+
+## The problem
+
+`remix_cli` installs one registry: a 15-token `theme` item and one authored
+recipe per component. Fortal is a second, complete design language on Remix,
+with 277 tokens, Radix color data, 35 recipes, and a parity contract against
+Radix Themes 3.3.0. Today an application can adopt Fortal only as a hosted
+package. It cannot own the source the way it owns the default recipes.
+
+This document designs the change that lets an application run
+`remix init --preset fortal` and then `remix add button`, and receive editable
+Fortal source under its own prefix. The final PR stack is Open Code, then Fortal, then the existing Sidebar PR.
+The Fortal PR preserves the five implementation commits described below.
+
+## Decisions
+
+These were reached in review before this document. Each one is stated once,
+with the reason, so a reviewer does not reopen it by accident.
+
+1. **A preset is a registry tree, chosen once at `init`.** A Fortal button
+ resolves Fortal tokens and a default button resolves default tokens, so the
+ two cannot mix in one application. The choice is recorded in `remix.yaml`,
+ next to the prefix, and follows the same rule as the prefix: it cannot
+ change after `init`. This is also how shadcn treats `style` in
+ `components.json`.
+2. **One recipe tree per preset.** shadcn v4 shares one component source across
+ styles because every style uses one variant vocabulary. Fortal's vocabulary
+ (`classic`, `solid`, `soft`, `surface`, `outline`, `ghost`) is not the
+ default's (`primary`, `secondary`, `outline`, `ghost`, `destructive`), so
+ a shared recipe with swappable data is not possible between these two.
+3. **Fortal is authored once, as Dart, in `packages/remix_fortal`.** The
+ `remix_cli` templates are derived from that source and committed. A check
+ fails when they drift. The alternative, hand-authored `.tmpl` files, means
+ editing 36 files without an analyzer, and running the 50 test files and
+ the parity checker through a temporary-app render on every change. shadcn
+ moved from hand-authored per-style trees to authored source plus a build
+ for the same reason.
+4. **The Radix color table installs as source.** It is one generated file
+ pinned to Radix Themes 3.3.0 that regenerates only on a reviewed pin move.
+ A package for data that never changes buys nothing, and the pub.dev name
+ `radix_colors` is already taken. Installing it lets an application add a
+ custom 12-step scale, which the closed accent enum forbids today.
+5. **Nothing installed is named Fortal.** The word appears in the registry
+ directory name and in `remix.yaml`. The application gets `AcmeScope`,
+ `AcmeTokens`, `AcmeButton`, and token ids such as `acme.accent.9`.
+6. **`mix_chart` stays a hosted dependency of the `chart` item.** It is the
+ chart engine the recipe styles, as `remix` is the button engine. The
+ default `chart` item already declares it.
+7. **`remix_fortal` leaves pub.dev after the published replacement works; the directory
+ stays.** Hosted Fortal has no consumer base to protect: 0 likes and 438
+ downloads at `1.0.0-beta.7`. pub.dev cannot delete a package, so the
+ package is marked discontinued with `remix init --preset fortal` named as
+ the replacement, after Commit 4 proves that path. The directory
+ `packages/remix_fortal` is not deleted. It is the authored source the
+ templates derive from, the target of 50 test files and the parity
+ checker, and a dependency of `apps/dashboard`, `apps/demo`, and
+ `apps/playground`. The source and template check detects byte drift: `--check` fails CI on any byte of difference, the same way
+ `docs/fortal/catalog.mdx` and the playground dogfood are checked today.
+
+## What the application gets
+
+```shell
+dart run remix_cli:remix init --prefix Acme --preset fortal
+dart run remix_cli:remix add button
+```
+
+```text
+lib/ui/
+ ui.dart
+ theme/
+ theme.dart barrel of the files below, minus radix_colors; exported from ui.dart
+ radix_colors.dart generated Radix 3.3.0 scales; not exported from ui.dart
+ computed.dart role math, shadows, token resolution
+ control_styles.dart shared focus and inset-surface helpers
+ tokens.dart AcmeTokens
+ theme_data.dart AcmeThemeConfig, AcmeThemeData, the token map
+ theme_scope.dart AcmeScope, AcmeTheme
+ surface_frame.dart
+ components/
+ base_button.dart shared Button and IconButton metrics and states
+ button.dart authored; edit this
+ button.g.dart generated; AcmeButton, AcmeButton.solid, ...
+```
+
+Hosted dependencies after install: `remix` and `mix_annotations`, with
+`build_runner` and `mix_generator` for generation. `remix_ui_icons` arrives
+only with `icons`. `mix_chart` arrives only with `chart`. No Fortal package.
+
+The application then edits the same three places the default preset offers:
+theme values in `theme_data.dart`, a recipe in `components/`, and a per-widget
+`style` argument that merges last.
+
+## The registry after this stack
+
+```text
+packages/remix_cli/lib/src/registry/
+ default/
+ registry.yaml moved, unchanged
+ templates//... moved, unchanged
+ fortal/
+ registry.yaml derived
+ templates/theme/*.dart.tmpl derived
+ templates//.dart.tmpl
+```
+
+`remix.yaml` gains one key at schema 2:
+
+```yaml
+schema: 2
+prefix: Acme
+preset: fortal
+paths:
+ ui: lib/ui
+```
+
+A schema 1 file has no `preset` key and reads as `preset: default`, so
+`apps/playground/remix.yaml` and every existing consumer keep working.
+
+## The Fortal source after this stack
+
+The package layout mirrors the installed layout. This is a hard requirement,
+not a nicety: derivation replaces every `Fortal` and `fortal` in a file, so a
+relative import such as `'../fortal/fortal.dart'` would break. After the move,
+no path segment under `lib/src` contains the word.
+
+| Today | After |
+|---|---|
+| `src/fortal/fortal_theme.dart` (2217 lines) | `src/theme/tokens.dart`, `src/theme/theme_data.dart`, `src/theme/theme_scope.dart`, `src/theme/control_styles.dart` |
+| `src/fortal/computed.dart` | `src/theme/computed.dart` |
+| `src/fortal/surface_frame.dart` | `src/theme/surface_frame.dart` |
+| `src/radix/colors/colors.dart` + `colors_generated.dart` | `src/theme/radix_colors.dart`, one file, written by `scripts/generate_radix_colors.dart` |
+| `src/fortal/base_button_recipe.dart` + `base_button_state_styles.dart` | `src/components/base_button.dart` |
+| `src/recipes/typography_shared.dart` | `src/components/typography.dart` |
+| `src/recipes/.dart` | `src/components/.dart` |
+| `src/radix/icons.dart` | `src/icons.dart` |
+| `src/fortal/fortal.dart` (internal barrel) | `src/theme/theme.dart`; exports every theme file except `radix_colors.dart` |
+
+The internal barrel survives on purpose. All 36 files under `recipes/`
+import it today, so the move is one path change per file instead of a
+36-file audit of which theme file each one uses. No recipe references a Radix color type, so the barrel
+does not export `radix_colors.dart`; `computed.dart` and `theme_data.dart`
+import that file directly.
+
+The public barrel `lib/remix_fortal.dart` keeps every export, so the public
+API does not change. `test/public_api_test.dart` proves it. Remix's own public
+barrel also re-exports the six Naked constructor/state types referenced by
+generated adapters, keeping installed recipes on the `package:remix/remix.dart`
+boundary.
+
+## Derivation
+
+`tool/build_fortal_preset.dart` at the workspace root, next to
+`check_open_code.dart`, does the following.
+
+**Input.** Every `.dart` file under `packages/remix_fortal/lib/src` except
+`*.g.dart`. The application generates its own parts.
+
+**Refusals, checked first.** Any path segment containing `fortal`. Any file
+containing `{{`, because the renderer treats that as a template token. Any
+`package:remix_fortal`, `package:mix`, or `package:naked_ui` import, because
+installed source must import the public `remix` surface. Each refusal names
+the file.
+
+**Substitution.** Plain `replaceAll` of `Fortal` with `{{typePrefix}}` and
+`fortal` with `{{valuePrefix}}`. This is the exact inverse of
+`TemplateRenderer.render`, so rendering with `Fortal` and `fortal` reproduces
+the source byte for byte. That round trip is the drift test. Token id strings
+follow the same rule and become `acme.accent.9` in the application. `Radix*`
+identifiers do not contain the word and pass through.
+
+**Placement.** `theme/*.dart` goes to `templates/theme/`. `components/x.dart`
+goes to `templates/x/x.dart.tmpl`. `icons.dart` is skipped: no Fortal recipe
+references an icon, and an icon alias set is not part of a design language,
+so the Fortal `icons` item is a copy of the default `icons` template. Both
+presets install the same file.
+
+**Registry.** The tool also writes `registry/fortal/registry.yaml`, so the
+item graph is correct by construction:
+
+- `registryDependencies` come from relative imports. Any `'../theme/...'`
+ import means `theme`, so the theme item is one node no matter which theme
+ file a recipe names. A sibling import such as `'checkbox.dart'` means
+ `checkbox`. Today those sibling imports are exactly: `data_table` to
+ `checkbox`, `icon_button`, and `select` (and transitively `base_button`
+ through `icon_button`); `sidebar` to `text` and `toggle`;
+ `text`, `code`, `heading`, `kbd`, and `link` to `typography`; `button` and
+ `icon_button` to `base_button`.
+- `generated` is present when the file has a `part 'x.g.dart';` line.
+- `dependencies` and `devDependencies` follow three rules, with every
+ constraint read from the default registry so the version floors live in
+ one place. `theme` declares `remix`. An item with a generated part declares
+ `mix_annotations`, `build_runner`, and `mix_generator`, as the default
+ `button` does. An item that imports `package:mix_chart` or
+ `package:remix_ui_icons` declares that package too. `base_button` and
+ `typography` have no part and no `@MixWidget`, so they declare nothing.
+- `exports` lists `theme/theme.dart` for the theme item and the recipe file
+ for every other item. `theme/radix_colors.dart` is never exported: it
+ declares bare top-level names such as `gray` and `red` that must not enter
+ `ui.dart`. `theme/computed.dart` is exported through the barrel; its names
+ are prefixed, and `chart.dart` calls `resolveFortalTokens` from it.
+
+**Modes.** No flag writes. `--check` derives in memory, compares with the
+committed files, and fails on any difference. `melos run open-code:fortal:check`
+runs it in CI, the way `docs:check` runs `generate_fortal_catalog.dart`.
+
+## The PR stack
+
+1. `feat/open-code` targets `main` in PR #177.
+2. `feat/fortal-presets` targets `feat/open-code` in PR #180.
+3. `feat/open-code-sidebar` targets `feat/fortal-presets` in PR #178.
+
+GitHub tracks these PRs as a native stack. Follow the merge and publication
+steps in [RELEASING.md](RELEASING.md).
+The original plan proposed five Fortal PRs. The final stack keeps those
+implementation boundaries as five commits in one Fortal PR.
+The Sidebar PR remains separate because it adds a default recipe.
+
+## Implementation commits
+
+The first two commits prepare Fortal source. The third adds preset selection.
+The fourth adds the derived preset and its consumer fixture. The fifth removes
+Fortal publication from repository automation. Package discontinuation remains
+a later release action.
+
+Two earlier drafts were folded in. The fresh-app proof is part of the PR
+that ships the Fortal preset, because a preset without its proof is not
+done. Docs land with the PR they describe, because `validate_docs.dart`
+already checks them and a flag without docs is not done either.
+
+### Commit 1: `refactor(remix_fortal)`: lay the source out like an installed preset
+
+A pure move. Git sees renames, reviewers see paths, and no behavior changes.
+This goes first so every later content diff lands on the final paths.
+
+- Apply the file table above. Split `fortal_theme.dart` into three files:
+ the `FortalTokens` class; the token-map builder with `FortalThemeConfig`
+ and `FortalThemeData`; and `FortalScope` with `FortalTheme`. The
+ declarations are not contiguous today, `FortalScope` sits between the
+ builder and the data classes, so this is an extraction by declaration, not
+ a slice by line range. Dart does not care about declaration order.
+- Merge the color pair into `theme/radix_colors.dart`. Point
+ `scripts/generate_radix_colors.dart` at the new path. Keep the file header
+ and the integrity hash.
+- Rename the internal barrel to `theme/theme.dart` and repoint every one of
+ the 36 files under `recipes/` that import it. Drop `radix_colors.dart` from
+ its exports.
+- `tool/generate_fortal_catalog.dart` hardcodes `lib/src/recipes/.dart`
+ in `_recipePath` and opens those files to render `docs/fortal/catalog.mdx`.
+ Point it at `components/`. This tool feeds `docs:check`, which the `ci`
+ aggregate runs, so leaving it breaks CI on the move.
+- The component docs link to recipe source on GitHub by path, for example
+ `docs/components/tooltip.mdx:126`. Twenty-five doc files carry such a
+ link. Rewrite every `lib/src/recipes/` link
+ to `lib/src/components/`. `validate_docs.dart` does not check these links,
+ so grep for the old path before merging.
+- Update the paths in `scripts/README.md`.
+- Proof: `test/public_api_test.dart` and `public_api_compatibility_test.dart`
+ unchanged and green; `melos run fortal:parity:check` green; `melos run
+ docs:check` green; the color fixture test still reads every scale; no
+ file in the repository outside `.context/` contains `src/recipes/` or
+ `src/fortal/`.
+
+### Commit 2: `feat(remix_fortal)`: instance style override, and only `remix` imports
+
+Content edits on the final paths. Every default component template takes a
+styler parameter merged last. No Fortal recipe does.
+
+- Add `Styler style = const Styler.create()` as the last
+ parameter of each of the 35 recipes, and `.merge(style)` as the last call.
+ Mirror `packages/remix_cli/lib/src/registry/templates/button/button.dart.tmpl`.
+- Twelve recipes already declare a local named `style`: `button`, `chart`,
+ `code`, `divider`, `heading`, `icon_button`, `kbd`, `link`, `select`,
+ `text`, `textfield`, `toggle`. Rename those locals first.
+- Remove the four unused `naked_ui` imports (`accordion`, `disclosure`,
+ `slider`, `tabs`; verified unused with the analyzer) and the four direct
+ `mix` imports (`chart`, `base_button`, `computed`, and the theme files).
+ `remix` re-exports `mix` in full. This is a precondition for Commit 4, whose
+ tool refuses those imports.
+- Regenerate the `.g.dart` parts. The generator adds `style` to every named
+ constructor.
+- Test: for Button, Checkbox, and TextField, a `style` that sets one idle
+ property wins over the recipe, and a hover fragment in `style` wins over
+ the recipe's hover fragment. Extend `fortal_control_matrix_test.dart` or add
+ a sibling.
+- Proof: `fortal:parity:check` and the package test suite.
+
+### Commit 3: `feat(remix_cli)`: preset axis
+
+The CLI learns that more than one registry exists. Only `default` ships in
+this PR, so behavior for every existing project is unchanged. This is
+independent of Commit 1 and Commit 2.
+
+- Move `lib/src/registry/registry.yaml` and `templates/` to
+ `lib/src/registry/default/`.
+- `registry.dart`: a `bundledPresets` constant listing `default`;
+ `loadBundled({required String preset})` resolves
+ `package:remix_cli/src/registry//registry.yaml`; an unknown preset
+ fails before any file read with a message that lists the bundled presets;
+ `RegistryCatalog.preset` for messages; the unknown-item error names the
+ preset.
+- `project_config.dart`: `preset` field; schema 2 requires the key; schema 1
+ reads as `default`; `encode()` writes schema 2; preset must match
+ `^[a-z][a-z0-9_]*$` and be in `bundledPresets`.
+- `cli.dart`: `init --preset `, default `default`.
+- `installer.dart`: `RegistryLoader` becomes
+ `Future Function(String preset)`; `add` passes
+ `config.preset`; the `init` mismatch error covers prefix, preset, and UI
+ path.
+- Repository tools that hardcode the old path: `tool/check_open_code.dart`,
+ `tool/check_open_code_dogfood.dart`, `tool/sync_registry_remix.dart`,
+ `tool/check_version_alignment.dart`, `tool/validate_docs.dart`, and
+ `test/tool/check_open_code_test.dart`. Point them at `registry/default/`.
+ `validate_docs.dart` matters most: it feeds `docs:check`, which the `ci`
+ aggregate runs, so missing it breaks CI on the move.
+- Tests: schema 1 parses as default; schema 2 without `preset` fails; bad
+ preset name fails; `init --preset default` writes schema 2; `init` with a
+ different preset on an existing project fails; `loadBundled` with an
+ unknown preset fails with the list; the installer passes the configured
+ preset to the loader; `--diff` still compares against the default tree.
+- Docs: the configuration section of `docs/open-code.mdx` shows the schema 2
+ file and the `--preset` flag, and drops the words "no presets".
+ `packages/remix_cli/CHANGELOG.md`.
+
+### Commit 4: `feat(remix_cli)`: the Fortal preset, with its proof
+
+Depends on Commit 1, 2, and 3. The diff is mostly derived content; review the
+tool, the registry rules, and the check.
+
+- `tool/build_fortal_preset.dart` as specified above, and
+ `test/tool/build_fortal_preset_test.dart` covering the refusals, the round
+ trip, the dependency inference, and `--check` on a planted drift.
+- The committed output: `registry/fortal/registry.yaml` and every template.
+- `bundledPresets` gains `fortal`. The `remix_cli` unit tests load the
+ Fortal catalog, resolve `button` to `theme`, `base_button`, `button`,
+ resolve `data_table` to its four dependencies, and rely on
+ `_validateGraphAndTargets` for cycles and double-owned targets.
+- Melos scripts `open-code:fortal:build` and `open-code:fortal:check`, named
+ like the existing `open-code:check`. The check joins the `ci` aggregate
+ next to `docs:check`. `.github/workflows/version.yml` runs
+ `sync_registry_remix.dart` as a shell step after `melos version`; add the
+ build right after it, because the Fortal registry copies its floors from
+ the default registry.
+- `tool/check_open_code.dart --preset fortal`: install every Fortal item
+ into a guarded temporary Flutter app with prefix `Acme`, generate without a
+ consumer `build.yaml`, analyze clean, and run a smoke test from
+ `open_code/fortal_fixture/` that pumps every generated widget under
+ `AcmeScope` and asserts a few resolved token values against known Radix
+ 3.3.0 hex values. No committed expected `.g.dart`: the package already
+ commits the same adapter under the `Fortal` prefix. No equivalence test
+ against `FortalButton`: the byte round trip already proves the recipe is
+ the same code, the same generator produces both adapters, and a temporary
+ app cannot path-depend on a `resolution: workspace` package cleanly.
+- Docs: a Fortal preset section in `docs/open-code.mdx` with the install and
+ the ownership boundary; `open_code/README.md`; a dated "Since" note in
+ `open_code/CLEAN_SHEET.md` under "What remains outside this MVP" linking
+ here; `packages/remix_cli/CHANGELOG.md`.
+
+### Commit 5: `chore(remix_fortal)`: stop publishing
+
+- `publish_to: none` in the pubspec. Remove the pub.dev publish notes from
+ the pubspec comments and the package from any release automation that
+ versions it.
+- Delete the hosted-pin check in `tool/fortal_parity/check.dart` and drop
+ `naked_ui` from the pubspec, which that check was the last reason to keep.
+- `docs/fortal/*` install section shows `remix init --preset fortal` and
+ drops `flutter pub add remix_fortal`.
+- After merge: mark `remix_fortal` discontinued on pub.dev with the preset
+ named as the replacement.
+
+## Risks and how each commit bounds them
+
+- **A `style` local collides with the new parameter.** Twelve recipes. Commit 2
+ renames them; the analyzer catches any miss.
+- **A file or path still contains `fortal` after Commit 1.** The derivation tool
+ refuses, and Commit 4's test plants one to prove the refusal.
+- **Formatter drift.** The installer formats rendered output, and wrapping
+ depends on prefix length, as `check_open_code_dogfood.dart` documents. The
+ drift check therefore compares the template to the source, never a rendered
+ file to a template.
+- **Version floors diverge between presets.** The Fortal registry copies its
+ constraints from the default registry at build time, and `--check` fails
+ when they differ.
+- **`remix.yaml` schema 2 reaches a consumer with an older CLI.** That CLI
+ rejects it with its existing "unsupported schema" error. The project-local
+ dev dependency pins the CLI, which is why the docs recommend it.
+
+## Consumer and release validation
+
+Both registries require Remix `^1.0.0-beta.9`. Published versions through beta.8
+lack the Naked UI types needed by generated Fortal adapters. The repository
+prepares beta.9 with these exports. Beta.8 already provides the Sidebar panel.
+
+`melos run open-code:check` installs every item from both presets against the
+checkout. `melos run open-code:release:check` requires hosted Remix and never
+falls back to the checkout. The CLI publish job depends on the hosted check.
+A source/template round trip does not replace either consumer check.
+
+The first public release also requires the CLI bootstrap and a hosted CLI
+installation check. Follow [the release instructions](RELEASING.md). Keep
+`remix_fortal` available until the published replacement passes those checks.
+
+## Out of this stack
+
+- `init --preset --reinstall`, a batched `add --overwrite` for switching
+ presets in place. shadcn does this. It is a later feature.
+- Preset knobs at `init`, such as `--accent indigo --radius medium` written
+ into the installed theme.
+- Authoring the default preset as Dart with the same derivation. The default
+ has no parity contract, so the pressure is lower. Reconsider after Commit 4
+ shows the tool's cost.
+- Deleting `packages/remix_fortal` from the repository. That would make the
+ `.tmpl` files the only form of Fortal, move the parity suite behind a
+ temporary-app render, and force `apps/dashboard`, `apps/demo`,
+ `apps/playground`, and 39 doc pages to migrate to installed source.
+ Reconsider only if the apps migrate to the preset on their own.
diff --git a/open_code/README.md b/open_code/README.md
index 5f21cd318..545b9477f 100644
--- a/open_code/README.md
+++ b/open_code/README.md
@@ -4,10 +4,12 @@ This example is installed by `remix_cli`. It gives an application editable
theme and component source while Remix continues to own rendering, interaction,
focus, loading, disabled behavior, and accessibility.
-The registry source lives in `packages/remix_cli/lib/src/registry/`. There is
-no second copy under `open_code/`. The files in `open_code/fixture/` are a
-fresh-app gallery, behavioral test suite, and expected generated adapter used
-by `tool/check_open_code.dart`.
+The two registry trees live in
+`packages/remix_cli/lib/src/registry/default/` and
+`packages/remix_cli/lib/src/registry/fortal/`. There is no second registry copy
+under `open_code/`. `open_code/fixture/` proves the default preset, while
+`open_code/fortal_fixture/` proves Fortal with every generated widget and
+known Radix Themes 3.3.0 color values.
## Install the CLI in a project
@@ -19,7 +21,7 @@ the CLI version and its bundled templates:
```shell
flutter pub add dev:remix_cli
-dart run remix_cli:remix init --prefix Acme
+dart run remix_cli:remix init --prefix Acme --preset default
dart run remix_cli:remix add button
```
@@ -30,6 +32,9 @@ the optional `chart` extension, plus `accordion`, `avatar`, `badge`, `button`,
`radio`, `segmented_control`, `select`, `skeleton`, `slider`, `spinner`,
`switch`, `tabs`, `textfield`, `toggle`, `toggle_group`, and `tooltip`.
+The `fortal` preset contains that complete surface plus `base_button`, `code`,
+`heading`, `kbd`, `sidebar`, `text`, and `typography`.
+
`chart` is the one optional extension outside the core Remix component
surface. It builds directly on `mix_chart`, installs no Fortal code, and
generates `AcmeLineChart`, `AcmeBarChart`, and `AcmePieChart` from one editable
@@ -42,6 +47,18 @@ at a checkout or staged package:
dart pub add "dev:remix_cli@{path: /path/to/remix/packages/remix_cli}"
```
+Both presets require Remix `^1.0.0-beta.9`. Until that release is available,
+add a temporary `pubspec_overrides.yaml` to the application:
+
+```yaml
+dependency_overrides:
+ remix:
+ path: /path/to/remix/packages/remix
+```
+
+Replace the path with your checkout. The [release instructions](RELEASING.md)
+cover hosted validation, CLI bootstrap, and the Fortal transition.
+
Two host notes travel with the catalog. An installed text field needs an
`Overlay` ancestor once it takes focus, for its selection handles —
`MaterialApp`, `CupertinoApp`, and any `WidgetsApp` with routes already provide
@@ -54,14 +71,35 @@ installed adapters inside them.
`init` creates `remix.yaml` and a managed barrel at `lib/ui/ui.dart`. The
default prefix is `Ui`; `--prefix Acme` produces names such as
`AcmeThemeScope`, `AcmeButton`, and `acmeButtonStyle`. A different source root
-can be chosen once with `--ui-path`.
+can be chosen once with `--ui-path`. The preset also is chosen once: existing
+projects default to `default`, and changing a configured preset is refused.
+
+## Fortal as application-owned source
+
+```shell
+dart run remix_cli:remix init --prefix Acme --preset fortal
+dart run remix_cli:remix add button
+```
+
+This installs the full Fortal theme and recipe as prefixed local source:
+`AcmeScope`, `AcmeTokens`, `AcmeButton`, and `acmeButtonStyle`. It also installs
+the shared `base_button.dart` implementation used by Button and IconButton.
+No installed identifier is named Fortal, and the consumer does not declare
+direct `mix`, `naked_ui`, or `remix_fortal` dependencies. `remix` owns behavior;
+the application owns the copied Radix color table, 277-token theme, component
+recipes, and generated adapters.
+
+The Fortal templates are derived from analyzed Dart in
+`packages/remix_fortal/lib/src/`; do not edit the committed `.tmpl` files by
+hand. `tool/build_fortal_preset.dart --check` makes source/template drift a CI
+failure.
The prefixes `Remix` and `Mix` are reserved for runtime dependencies.
Use an application prefix such as `Ui` or `Acme`.
-`add button` resolves the Theme dependency first, adds compatible hosted
-package constraints when they are missing, writes the authored source, updates
-the managed barrel, and generates only the declared adapter:
+The default preset installs Theme before Button. The command adds missing
+dependency constraints, writes source, updates the barrel, and generates the
+adapter:
```text
lib/ui/
@@ -75,11 +113,15 @@ lib/ui/
button.g.dart generated; do not hand-edit
```
-Every later component `add` drops one more pair into `components/` and extends
+Each later default component `add` adds another pair to `components/` and extends
the barrel's managed block. Existing authored source stays untouched. The
focused build also includes every installed generated adapter, so a dependency
change cannot remove an earlier generated part.
+Fortal also installs shared source items without generated parts, such as
+`base_button` and `typography`. Use `add --dry-run` to inspect each
+preset's files and dependencies.
+
`dart run remix_cli:remix add icons` instead adds `lib/ui/icons.dart`, declares
`remix_ui_icons`, and exposes a small, application-owned `UiIcons` alias set
without a generated adapter. Add or rename aliases there as the application
@@ -215,11 +257,15 @@ root:
```shell
fvm dart test test/tool/check_open_code_test.dart
-fvm dart run tool/check_open_code.dart
+fvm dart run melos run open-code:check
```
-The full check creates a guarded temporary Flutter app, installs the checkout
-CLI with prefix `Acme`, adds every registry item, and proves generation,
-analysis, and all behavior tests against hosted Remix. It then overrides Remix to the current checkout,
-regenerates without a consumer `build.yaml`, and repeats the checks. Pass
-`--keep` to retain the generated gallery for inspection.
+The CI check creates a fresh application for each preset. It installs the
+checkout CLI with prefix `Acme`, adds every item, and verifies generation,
+analysis, and consumer tests against checkout Remix.
+
+After Remix is published, run `fvm dart run melos run open-code:release:check`
+to verify both presets with hosted Remix. The direct checker defaults to both
+sources. Select `--source checkout` or `--source hosted` for one source.
+Use `--hosted-cli --source hosted` after CLI publication to verify its hosted
+assets. Pass `--keep` to retain a generated application for inspection.
diff --git a/open_code/RELEASING.md b/open_code/RELEASING.md
new file mode 100644
index 000000000..5acffa4c1
--- /dev/null
+++ b/open_code/RELEASING.md
@@ -0,0 +1,104 @@
+# Release the Open Code presets
+
+## Current state
+
+The stack prepares Remix `1.0.0-beta.9` and `remix_cli` `0.1.0`.
+Neither version has been published from this stack. The existing
+`remix_fortal` release remains available until the replacement passes validation.
+
+Remix beta.8 was published from PR #182 on September 5, 2026. It does not
+include the exports required by the Fortal preset. This stack requires a new release.
+
+The native GitHub stack contains [#177](https://github.com/conceptadev/remix/pull/177),
+[#180](https://github.com/conceptadev/remix/pull/180), and
+[#178](https://github.com/conceptadev/remix/pull/178), from bottom to top.
+Review each PR against its parent. Require passing checks for every PR.
+
+After merge approval, merge the stack through the top PR, #178.
+GitHub merges all three PRs into `main`. For a partial merge, start with #177.
+GitHub automatically rebases the remaining branches and updates their targets.
+Require passing checks after any rebase. See the
+[GitHub stack instructions](https://docs.github.com/en/pull-requests/get-started/about-stacked-prs#merging).
+
+## Validate the release candidate
+
+Run these commands from the final checkout with Flutter 3.44.0:
+
+```shell
+fvm flutter pub get
+fvm dart run melos run ci
+fvm dart analyze
+```
+
+CI installs every item from both presets in fresh applications. It uses
+checkout Remix and hosted supporting packages. The source and template checks
+also verify generated files, the Radix contract, and the catalog.
+
+Run `fvm dart pub publish --dry-run` inside `packages/remix` and
+`packages/remix_cli`. Confirm that the CLI archive includes both registry trees
+and their templates. Resolve every publication error before continuing.
+
+The version and changelog for beta.9 are already prepared in this stack.
+Future releases use `.github/workflows/version.yml` to update Remix and both
+registry minimum versions together.
+
+## Publish Remix first
+
+After release approval, tag the validated merge commit as `v1.0.0-beta.9` and
+push that tag alone. The existing publish workflow publishes Remix.
+After success, record `remix-v1.0.0-beta.9` on the same commit for Melos history.
+
+Wait until pub.dev serves beta.9. Then run:
+
+```shell
+fvm dart run melos run open-code:release:check
+```
+
+This command installs both presets with hosted Remix and the checkout CLI.
+It must pass before CLI publication. It never substitutes checkout Remix for
+a missing hosted release. Keep the registry minimum at beta.9 if resolution fails.
+
+## Bootstrap the CLI package
+
+The first CLI publication requires an authorized uploader to run
+`fvm dart pub publish` inside `packages/remix_cli` after the hosted checks pass.
+Pub.dev requires a manual first publication before automated publishing can
+be configured. See the [Dart publication instructions](https://dart.dev/tools/pub/automated-publishing).
+
+Confirm the intended publisher. Then configure automated publishing in the
+package Admin tab with repository `conceptadev/remix` and tag pattern
+`remix_cli-v{{version}}`.
+
+Do not push `remix_cli-v0.1.0` to publish the same version again. Future CLI
+releases update the package version, `lib/src/version.dart`, and the changelog.
+Their tag starts the hosted consumer checks before the publish job.
+
+## Verify the published CLI
+
+After pub.dev serves CLI `0.1.0`, run:
+
+```shell
+fvm dart run tool/check_open_code.dart --source hosted --hosted-cli
+fvm dart run tool/check_open_code.dart --preset fortal --source hosted --hosted-cli
+```
+
+These checks install the exact CLI version declared in the checkout from
+pub.dev. They require hosted runtime packages and reject checkout package
+dependencies. They exercise the published registry assets, generation, analyzer,
+and consumer tests.
+
+Remove the pending-publication notices from the installation guides after
+these checks pass.
+
+## Discontinue the old Fortal package
+
+After the published CLI checks pass, mark `remix_fortal` discontinued in its
+pub.dev Admin tab. Name `remix_cli` as the replacement package. The migration
+instructions use `remix init --preset fortal` and application-owned imports.
+
+Keep `packages/remix_fortal` in the repository as the analyzed authoring source.
+Keep `publish_to: none` and its tests. Existing hosted installations remain
+available; discontinuation does not delete their package versions.
+
+If either published consumer check fails, keep `remix_fortal` active and fix
+the replacement before completing the transition.
diff --git a/open_code/fortal_fixture/analysis_options.yaml b/open_code/fortal_fixture/analysis_options.yaml
new file mode 100644
index 000000000..f20a583c8
--- /dev/null
+++ b/open_code/fortal_fixture/analysis_options.yaml
@@ -0,0 +1,24 @@
+# Keep the consumer fixture dependency-free while treating copied-source drift
+# as an error. Generated Mix adapters are covered by compilation and tests.
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
+ errors:
+ unused_import: error
+ unused_element: error
+ exclude:
+ - build/**
+ - lib/ui/**/*.g.dart
+
+linter:
+ rules:
+ - always_declare_return_types
+ - avoid_print
+ - dangling_library_doc_comments
+ - overridden_fields
+ - prefer_const_constructors
+ - prefer_const_declarations
+ - prefer_final_locals
+ - unnecessary_library_name
+ - unnecessary_parenthesis
diff --git a/open_code/fortal_fixture/lib/main.dart b/open_code/fortal_fixture/lib/main.dart
new file mode 100644
index 000000000..6700bd045
--- /dev/null
+++ b/open_code/fortal_fixture/lib/main.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+
+import 'ui/ui.dart';
+
+/// Runs a small application composed entirely from installed Fortal source.
+void main() => runApp(const AcmeApp());
+
+/// Minimal consumer shell for the Fortal preset.
+class AcmeApp extends StatelessWidget {
+ /// Creates the example application.
+ const AcmeApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ debugShowCheckedModeBanner: false,
+ home: AcmeScope(
+ child: Scaffold(
+ body: Center(
+ child: AcmeCard(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ AcmeHeading('Fortal, now yours'),
+ AcmeText('Radix Themes 3.3.0 expressed as local Dart.'),
+ AcmeButton(label: 'Continue'),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/open_code/fortal_fixture/pubspec.yaml b/open_code/fortal_fixture/pubspec.yaml
new file mode 100644
index 000000000..17aa9dc52
--- /dev/null
+++ b/open_code/fortal_fixture/pubspec.yaml
@@ -0,0 +1,20 @@
+name: open_code_fixture
+description: A fresh application that proves the Fortal preset installs as owned source.
+publish_to: "none"
+version: 0.1.0
+
+environment:
+ sdk: ">=3.12.0 <4.0.0"
+ flutter: ">=3.44.0"
+
+# Deliberately pre-install: remix_cli must add the complete dependency surface.
+dependencies:
+ flutter:
+ sdk: flutter
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
diff --git a/open_code/fortal_fixture/test/open_code_test.dart b/open_code/fortal_fixture/test/open_code_test.dart
new file mode 100644
index 000000000..76c57f79a
--- /dev/null
+++ b/open_code/fortal_fixture/test/open_code_test.dart
@@ -0,0 +1,317 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mix_chart/mix_chart.dart';
+import 'package:open_code_fixture/main.dart';
+import 'package:open_code_fixture/ui/ui.dart';
+import 'package:remix/remix.dart';
+
+void main() {
+ test('the default palette retains its Radix Themes 3.3.0 values', () {
+ final colors = resolveAcmeTokens(const AcmeThemeConfig());
+
+ expect(colors.colorBackground, const Color(0xFFFFFFFF));
+ expect(colors.accent.scale.step(9), const Color(0xFF3E63DD));
+ expect(colors.gray.scale.step(12), const Color(0xFF1C2024));
+ });
+
+ testWidgets('the example application renders installed source', (
+ tester,
+ ) async {
+ await tester.pumpWidget(const AcmeApp());
+
+ expect(find.byType(AcmeCard), findsOneWidget);
+ expect(find.byType(AcmeButton), findsOneWidget);
+ expect(find.text('Fortal, now yours'), findsOneWidget);
+ expect(tester.takeException(), isNull);
+ });
+
+ testWidgets('every generated Fortal widget builds below AcmeScope', (
+ tester,
+ ) async {
+ final cases = _generatedWidgetCases();
+ final seen = {};
+
+ for (final widgetCase in cases) {
+ await tester.pumpWidget(_host(widgetCase.child));
+ await tester.pump();
+
+ for (final type in widgetCase.generatedTypes) {
+ expect(
+ find.byType(type),
+ findsOneWidget,
+ reason: '${widgetCase.name} must build $type',
+ );
+ seen.add(type);
+ }
+ expect(
+ tester.takeException(),
+ isNull,
+ reason: '${widgetCase.name} must build without framework errors',
+ );
+ }
+
+ expect(seen, _allGeneratedWidgetTypes);
+ await tester.pumpWidget(const SizedBox.shrink());
+ });
+}
+
+Widget _host(Widget child) {
+ return MaterialApp(
+ home: AcmeScope(
+ child: Scaffold(
+ body: Center(child: SizedBox(width: 640, height: 480, child: child)),
+ ),
+ ),
+ );
+}
+
+List<_WidgetCase> _generatedWidgetCases() => [
+ _WidgetCase(
+ 'accordion',
+ RemixAccordionGroup(
+ controller: RemixAccordionController(),
+ child: const AcmeAccordion(
+ value: 'item',
+ title: 'Item',
+ child: Text('Content'),
+ ),
+ ),
+ {AcmeAccordion},
+ ),
+ const _WidgetCase('avatar', AcmeAvatar(label: 'AC'), {AcmeAvatar}),
+ const _WidgetCase('badge', AcmeBadge(label: 'New'), {AcmeBadge}),
+ const _WidgetCase('button', AcmeButton(label: 'Save'), {AcmeButton}),
+ const _WidgetCase('callout', AcmeCallout(text: 'Notice'), {AcmeCallout}),
+ const _WidgetCase('card', AcmeCard(child: Text('Card')), {AcmeCard}),
+ _WidgetCase(
+ 'line chart',
+ AcmeLineChart(
+ series: [
+ LineSeries(
+ id: 'revenue',
+ label: 'Revenue',
+ points: [ChartPoint(id: 'monday', x: 0, y: 18)],
+ ),
+ ],
+ ),
+ const {AcmeLineChart},
+ ),
+ _WidgetCase(
+ 'bar chart',
+ AcmeBarChart(
+ groups: [
+ BarGroup(
+ id: 'q1',
+ label: 'Q1',
+ bars: [BarValue(id: 'actual', label: 'Actual', toY: 42)],
+ ),
+ ],
+ ),
+ const {AcmeBarChart},
+ ),
+ _WidgetCase(
+ 'pie chart',
+ AcmePieChart(
+ slices: [PieSlice(id: 'direct', label: 'Direct', value: 64)],
+ ),
+ const {AcmePieChart},
+ ),
+ const _WidgetCase(
+ 'checkbox',
+ AcmeCheckbox(selected: false, label: 'Choice'),
+ {AcmeCheckbox},
+ ),
+ const _WidgetCase(
+ 'checkbox group item',
+ RemixCheckboxGroup(
+ values: {},
+ child: AcmeCheckboxGroupItem(value: 'a', label: 'Choice A'),
+ ),
+ {AcmeCheckboxGroupItem},
+ ),
+ const _WidgetCase(
+ 'data list',
+ AcmeDataList(
+ items: [RemixDataListItem(label: 'Name', value: 'Ada')],
+ ),
+ {AcmeDataList},
+ ),
+ const _WidgetCase(
+ 'data table',
+ AcmeDataTable(
+ rows: [],
+ columns: [
+ RemixDataTableColumn(
+ id: 'value',
+ label: 'Value',
+ cellBuilder: _stringCell,
+ ),
+ ],
+ ),
+ {AcmeDataTable},
+ ),
+ const _WidgetCase('dialog', AcmeDialog(title: 'Dialog'), {AcmeDialog}),
+ const _WidgetCase(
+ 'disclosure',
+ AcmeDisclosure(trigger: Text('More'), content: Text('Details')),
+ {AcmeDisclosure},
+ ),
+ const _WidgetCase('divider', AcmeDivider(), {AcmeDivider}),
+ const _WidgetCase(
+ 'icon button',
+ AcmeIconButton(icon: Icons.add, semanticLabel: 'Add'),
+ {AcmeIconButton},
+ ),
+ const _WidgetCase(
+ 'menu',
+ AcmeMenu(
+ trigger: RemixMenuTrigger(label: 'Menu'),
+ items: [RemixMenuItem(value: 'a', label: 'A')],
+ ),
+ {AcmeMenu},
+ ),
+ const _WidgetCase(
+ 'popover',
+ AcmePopover(popoverChild: Text('Content'), child: Text('Open')),
+ {AcmePopover},
+ ),
+ const _WidgetCase('progress', AcmeProgress(value: 0.5), {AcmeProgress}),
+ const _WidgetCase(
+ 'radio',
+ RemixRadioGroup(
+ groupValue: 'a',
+ child: AcmeRadio(value: 'a', semanticLabel: 'Choice A'),
+ ),
+ {AcmeRadio},
+ ),
+ const _WidgetCase(
+ 'segmented control',
+ AcmeSegmentedControl(
+ items: [RemixSegmentedControlItem(value: 'a', label: 'A')],
+ selectedValue: 'a',
+ ),
+ {AcmeSegmentedControl},
+ ),
+ const _WidgetCase(
+ 'select',
+ AcmeSelect(
+ trigger: RemixSelectTrigger(placeholder: 'Pick'),
+ items: [RemixSelectItem(value: 'a', label: 'A')],
+ ),
+ {AcmeSelect},
+ ),
+ const _WidgetCase(
+ 'sidebar',
+ AcmeSidebar(
+ sections: [
+ RemixSidebarSection(
+ destinations: [
+ RemixSidebarDestination(value: 'overview', label: 'Overview'),
+ ],
+ ),
+ ],
+ selectedValue: 'overview',
+ ),
+ {AcmeSidebar},
+ ),
+ const _WidgetCase(
+ 'skeleton',
+ AcmeSkeleton(child: SizedBox(width: 80, height: 20)),
+ {AcmeSkeleton},
+ ),
+ const _WidgetCase('slider', AcmeSlider(value: 0.5), {AcmeSlider}),
+ const _WidgetCase('spinner', AcmeSpinner(), {AcmeSpinner}),
+ const _WidgetCase(
+ 'switch',
+ AcmeSwitch(selected: false, semanticLabel: 'Notifications'),
+ {AcmeSwitch},
+ ),
+ const _WidgetCase(
+ 'tabs',
+ RemixTabs(
+ selectedTabId: 'a',
+ child: Column(
+ children: [
+ AcmeTabBar(
+ child: Row(
+ children: [AcmeTab(tabId: 'a', label: 'A')],
+ ),
+ ),
+ AcmeTabView(tabId: 'a', child: Text('A view')),
+ ],
+ ),
+ ),
+ {AcmeTabBar, AcmeTab, AcmeTabView},
+ ),
+ const _WidgetCase('text', AcmeText('Body'), {AcmeText}),
+ const _WidgetCase('text field', AcmeTextField(label: 'Name'), {
+ AcmeTextField,
+ }),
+ const _WidgetCase('text area', AcmeTextArea(label: 'Notes'), {AcmeTextArea}),
+ const _WidgetCase('toggle', AcmeToggle(selected: false, label: 'Bold'), {
+ AcmeToggle,
+ }),
+ const _WidgetCase(
+ 'toggle group',
+ AcmeToggleGroup(
+ items: [RemixToggleGroupItem(value: 'a', label: 'A')],
+ selectedValue: 'a',
+ ),
+ {AcmeToggleGroup},
+ ),
+ const _WidgetCase(
+ 'tooltip',
+ AcmeTooltip(tooltipChild: Text('Help'), child: Text('Hover')),
+ {AcmeTooltip},
+ ),
+];
+
+const _allGeneratedWidgetTypes = {
+ AcmeAccordion,
+ AcmeAvatar,
+ AcmeBadge,
+ AcmeButton,
+ AcmeCallout,
+ AcmeCard,
+ AcmeLineChart,
+ AcmeBarChart,
+ AcmePieChart,
+ AcmeCheckbox,
+ AcmeCheckboxGroupItem,
+ AcmeDataList,
+ AcmeDataTable,
+ AcmeDialog,
+ AcmeDisclosure,
+ AcmeDivider,
+ AcmeIconButton,
+ AcmeMenu,
+ AcmePopover,
+ AcmeProgress,
+ AcmeRadio,
+ AcmeSegmentedControl,
+ AcmeSelect,
+ AcmeSidebar,
+ AcmeSkeleton,
+ AcmeSlider,
+ AcmeSpinner,
+ AcmeSwitch,
+ AcmeTabBar,
+ AcmeTab,
+ AcmeTabView,
+ AcmeText,
+ AcmeTextField,
+ AcmeTextArea,
+ AcmeToggle,
+ AcmeToggleGroup,
+ AcmeTooltip,
+};
+
+final class _WidgetCase {
+ const _WidgetCase(this.name, this.child, this.generatedTypes);
+
+ final String name;
+ final Widget child;
+ final Set generatedTypes;
+}
+
+Widget _stringCell(BuildContext context, String value) => Text(value);
diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md
index c2af8f144..b5171a1f7 100644
--- a/packages/remix/CHANGELOG.md
+++ b/packages/remix/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.0.0-beta.9
+
+- Export the Naked UI constructor and state types required by generated
+ application-owned Accordion, Disclosure, Slider, and Tabs recipes.
+
## 1.0.0-beta.8
- **FEAT**(sidebar): publish a Remix sidebar panel (#168).
diff --git a/packages/remix/README.md b/packages/remix/README.md
index 82b9714c6..615f8b4b9 100644
--- a/packages/remix/README.md
+++ b/packages/remix/README.md
@@ -163,10 +163,17 @@ final destructiveButton = baseButtonStyle
## Theming with Fortal
Remix ships no theme of its own. If you want a polished, Radix Themes-inspired
-starting point instead of authoring every style yourself, add the companion
-[`remix_fortal`](https://pub.dev/packages/remix_fortal) package — it provides
-`FortalScope`, a token system, the `fortal*Style()` recipes, and a matching
-catalog of ready-made `Fortal*` widgets.
+starting point instead of authoring every style yourself, initialize the
+application-owned Fortal preset:
+
+```bash
+flutter pub add dev:remix_cli
+dart run remix_cli:remix init --prefix Fortal --preset fortal
+dart run remix_cli:remix add button
+```
+
+It copies `FortalScope`, the token system, `fortal*Style()` recipes, and the
+components you add into your application.
See the [Fortal documentation](https://docs.page/btwld/remix/fortal) to get
started.
@@ -179,7 +186,7 @@ Remix composes inside your existing Flutter host. It does not require a
| UI | Caller provides | Compatible hosts |
|----|-----------------|------------------|
| Ordinary `Remix*` widgets | The inherited Flutter services used by the widget subtree | Material, Cupertino, Widgets, and router-based hosts |
-| Widgets styled by a theme package such as Fortal | That package's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
+| Widgets styled by the Fortal preset | The preset's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
| Menu, select, popover, and tooltip | An `Overlay` | Any host exposing an overlay; use `Overlay.wrap` when no `Navigator` is needed |
| `showRemixDialog` and `showRemixAlertDialog` | A `Navigator` | Any host with a caller-owned navigator |
diff --git a/packages/remix/example/main.dart b/packages/remix/example/main.dart
index a9b1f90f0..329385f9c 100644
--- a/packages/remix/example/main.dart
+++ b/packages/remix/example/main.dart
@@ -8,7 +8,8 @@ void main() {
/// Remix ships no theme: every visual decision below is a styler you author.
///
/// If you would rather start from a ready-made Radix Themes-inspired palette,
-/// add the companion `remix_fortal` package and wrap your app in `FortalScope`.
+/// initialize the `fortal` preset with `remix_cli` and wrap your app in the
+/// installed scope.
class RemixExampleApp extends StatelessWidget {
const RemixExampleApp({super.key});
diff --git a/packages/remix/lib/remix.dart b/packages/remix/lib/remix.dart
index bcbf5f777..13ce84c77 100644
--- a/packages/remix/lib/remix.dart
+++ b/packages/remix/lib/remix.dart
@@ -1,7 +1,7 @@
/// Remix design system widgets and styles.
///
-/// Remix ships no theme. For a ready-made Radix Themes-inspired preset, add the
-/// companion `remix_fortal` package.
+/// Remix ships no theme. For a ready-made Radix Themes-inspired starting point,
+/// initialize the application-owned `fortal` preset with `remix_cli`.
library remix;
/// COMPONENTS
@@ -40,6 +40,12 @@ export 'src/components/tooltip/tooltip.dart';
export 'package:mix/mix.dart';
export 'package:naked_ui/naked_ui.dart'
show
+ NakedAccordionTriggerBuilder,
+ NakedAccordionItemState,
+ NakedDisclosureState,
+ NakedDisclosureTransitionBuilder,
+ NakedSliderSemanticFormatterCallback,
+ NakedTabState,
NakedMenuState,
OverlayAlignment,
OverlayPlacement,
diff --git a/packages/remix/pubspec.yaml b/packages/remix/pubspec.yaml
index 87fe1b36a..a7c62a3c6 100644
--- a/packages/remix/pubspec.yaml
+++ b/packages/remix/pubspec.yaml
@@ -12,7 +12,7 @@ topics:
- styling
- mix
-version: 1.0.0-beta.8
+version: 1.0.0-beta.9
# Consumer floor, matching `mix` and `naked_ui`. Intentionally lower than the
# workspace's own toolchain floor: dev tooling needs a newer SDK than the
diff --git a/packages/remix/test/components/slider/slider_widget_test.dart b/packages/remix/test/components/slider/slider_widget_test.dart
index bf9bd335f..4057792a2 100644
--- a/packages/remix/test/components/slider/slider_widget_test.dart
+++ b/packages/remix/test/components/slider/slider_widget_test.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:naked_ui/naked_ui.dart';
import 'package:remix/remix.dart';
import 'package:remix/src/rendering/remix_box_effects.dart'
show RemixBoxAdapter;
diff --git a/packages/remix_cli/CHANGELOG.md b/packages/remix_cli/CHANGELOG.md
index 34dbb689d..68ef1ca22 100644
--- a/packages/remix_cli/CHANGELOG.md
+++ b/packages/remix_cli/CHANGELOG.md
@@ -1,12 +1,22 @@
## 0.1.0
+- Require Remix `^1.0.0-beta.9` for the generated Fortal adapters. CI checks
+ both presets with checkout Remix. Publication requires the hosted check.
+
+- Adds the `fortal` preset as application-owned source derived from the
+ repository's analyzed Radix Themes 3.3.0 implementation. It installs a
+ prefixed 277-token theme and full component catalog without a
+ `remix_fortal`, direct `mix`, or direct `naked_ui` dependency.
+- Adds a preset axis to `remix init`. New configurations use schema 2 and
+ record `preset: default`; schema 1 configurations remain compatible and
+ read as the default preset.
- Initial project-local CLI for installing editable Remix UI source.
- Uses the Flutter SDK and its Dart executable on Windows. CI verifies installation with the real SDK.
- Supports explicit, glob, and nested workspace members through Pub's resolved package configuration.
- Rejects reserved runtime prefixes before writing source and prints command help once.
- Preserves spaces and special characters in generation filters. Each filter selects one generated file.
- Bundles theme plus a registry item for every styled component surface in
- `remix` 1.0.0-beta.7: accordion, avatar, badge, button, callout, card,
+ `remix`: accordion, avatar, badge, button, callout, card,
checkbox, data_list,
data_table, dialog, disclosure, divider, icon_button, link, menu, popover,
progress, radio, segmented_control, select, skeleton, slider, spinner,
diff --git a/packages/remix_cli/README.md b/packages/remix_cli/README.md
index 152771f66..d3ed2bd0e 100644
--- a/packages/remix_cli/README.md
+++ b/packages/remix_cli/README.md
@@ -1,8 +1,10 @@
# remix_cli
`remix_cli` installs editable component source into a Flutter application.
-Remix remains the behavior dependency; the application owns its tokens, theme
-values, component recipes, and generated adapters.
+Choose the compact `default` preset or the Radix Themes-inspired `fortal`
+preset at initialization. Remix remains the behavior dependency; the
+application owns its tokens, theme values, component recipes, and generated
+adapters.
The 0.1.0 catalog includes an opt-in `icons` seam and covers the core Remix
component surface: `accordion`, `avatar`, `badge`, `button`, `callout`, `card`,
@@ -10,7 +12,9 @@ component surface: `accordion`, `avatar`, `badge`, `button`, `callout`, `card`,
`icon_button`, `link`, `menu`, `popover`, `progress`, `radio`,
`segmented_control`, `select`, `skeleton`, `slider`, `spinner`, `switch`,
`tabs`, `textfield`, `toggle`, `toggle_group`, and `tooltip`, each depending on
-`theme`. `data_table` also depends on `checkbox`, `icon_button`, and `select`,
+`theme`. The Fortal preset contains the same surface plus `base_button`,
+`code`, `heading`, `kbd`, `sidebar`, `text`, and `typography`. `data_table`
+also depends on `checkbox`, `icon_button`, and `select`,
because its selection column, pager, and page-size control are those
components. The catalog also offers `chart` as an optional extension over
`mix_chart`; it does not depend on `remix_fortal`. There is no remote registry,
@@ -26,7 +30,7 @@ pins the CLI version and its bundled templates:
```shell
flutter pub add dev:remix_cli
-dart run remix_cli:remix init --prefix Acme
+dart run remix_cli:remix init --prefix Acme --preset fortal
dart run remix_cli:remix add button
```
@@ -36,6 +40,18 @@ To run an unreleased build, point at a checkout or staged package path:
dart pub add "dev:remix_cli@{path: /path/to/remix/packages/remix_cli}"
```
+Both presets require Remix `^1.0.0-beta.9`. Before that release is available,
+add this `pubspec_overrides.yaml` to the application:
+
+```yaml
+dependency_overrides:
+ remix:
+ path: /path/to/remix/packages/remix
+```
+
+Replace the path with your checkout. Remove the override after beta.9 is
+published and run `flutter pub get` to use hosted Remix.
+
Global activation is convenient for experiments, but it does not pin the
template version per project:
@@ -43,8 +59,8 @@ template version per project:
dart pub global activate remix_cli
```
-`remix_cli` is versioned independently of `remix` and `remix_fortal`, so its
-version never matches theirs. Its registry is authored against one `remix`
+`remix_cli` is versioned independently of `remix`, so its version does not
+match Remix's. Its registry is authored against one `remix`
version; `add` reports when your resolved `remix` is newer than that one.
## Initialize a project
@@ -58,8 +74,9 @@ dart run remix_cli:remix init
The default configuration is:
```yaml
-schema: 1
+schema: 2
prefix: Ui
+preset: default
paths:
ui: lib/ui
```
@@ -67,12 +84,29 @@ paths:
Customize it only at initialization:
```shell
-dart run remix_cli:remix init --prefix Acme --ui-path lib/design_system
+dart run remix_cli:remix init --prefix Acme --preset fortal --ui-path lib/design_system
```
`init` validates the Flutter project, writes `remix.yaml`, and creates a barrel
with a managed export block. Repeating the same command is safe. A different
configuration is refused instead of silently rewriting an existing project.
+The preset defaults to `default` when omitted and cannot be changed after
+initialization.
+
+### Fortal as owned source
+
+With `preset: fortal`, `add button` installs the full local theme layer,
+`base_button.dart`, `button.dart`, and the generated adapter. The configured
+prefix replaces Fortal throughout: `--prefix Acme` creates `AcmeScope`,
+`AcmeTokens`, `AcmeButton`, and `acmeButtonStyle`. The installed application
+does not depend directly on `mix`, `naked_ui`, or `remix_fortal`; it can edit
+the Radix color data, tokens, recipes, and instance overrides as ordinary app
+source.
+
+Place `AcmeScope` below the application host. For a routed Material app, wrap
+the child of `MaterialApp.builder` with `AcmeScope`. Routes and dialogs then
+inherit its tokens and text defaults. See `lib/ui/theme/theme_scope.dart` for
+the scope example.
The prefixes `Remix` and `Mix` are reserved for runtime dependencies.
Use an application prefix such as `Ui` or `Acme`.
@@ -88,7 +122,7 @@ dependencies, formats the authored files, runs generation only for the
declared `button.g.dart`, and analyzes the installed UI path. It does not create
or modify `build.yaml`.
-With the default path, the application receives:
+With the default preset and path, the application receives:
```text
lib/ui/
@@ -102,11 +136,13 @@ lib/ui/
button.g.dart
```
-One `add` installs one item. Repeat it for each component you want; every
-component item drops one authored file and one generated part into
-`components/` and extends the barrel. Existing authored source stays
-untouched. The focused build also includes every installed generated adapter,
-so a dependency change cannot remove an earlier generated part.
+One `add` installs one item and its registry dependencies. Repeat it for each
+component you need. Default component items add an authored file and a
+generated part to `components/`. Fortal also has shared source items without
+generated parts, such as `base_button` and `typography`.
+Existing authored source stays untouched. When generation runs, the build
+includes every installed adapter. This protects earlier generated parts when
+dependencies change.
`dart run remix_cli:remix add icons` is the deliberate exception. It adds
`lib/ui/icons.dart`, declares `remix_ui_icons`, and exposes a small,
@@ -114,7 +150,8 @@ application-owned `UiIcons` alias set with no generated adapter. Add or rename
aliases there as your interface evolves. The complete 318-icon catalog remains
one direct `package:remix_ui_icons/remix_ui_icons.dart` import away.
-Most items generate one widget. Four do not: `checkbox` also generates
+Most default component items generate one widget. Four generate several:
+`checkbox` also generates
`UiCheckboxGroupItem`, `textfield` generates `UiTextField` and `UiTextArea`,
`tabs` generates `UiTabBar`, `UiTab`, and `UiTabView`, and `chart` generates
`UiLineChart`, `UiBarChart`, and `UiPieChart`.
diff --git a/packages/remix_cli/lib/src/cli.dart b/packages/remix_cli/lib/src/cli.dart
index 0aabc822c..9c8c42258 100644
--- a/packages/remix_cli/lib/src/cli.dart
+++ b/packages/remix_cli/lib/src/cli.dart
@@ -17,9 +17,14 @@ typedef InitHandler = Future Function(InitOptions options);
typedef AddHandler = Future Function(AddOptions options);
final class InitOptions {
- const InitOptions({required this.prefix, required this.uiPath});
+ const InitOptions({
+ required this.prefix,
+ required this.preset,
+ required this.uiPath,
+ });
final String prefix;
+ final String preset;
final String uiPath;
}
@@ -122,6 +127,7 @@ final class _InitCommand extends Command {
_InitCommand(this._handler) {
argParser
..addOption('prefix', defaultsTo: 'Ui')
+ ..addOption('preset', defaultsTo: 'default')
..addOption('ui-path', defaultsTo: 'lib/ui');
}
@@ -145,6 +151,7 @@ final class _InitCommand extends Command {
await handler(
InitOptions(
prefix: argResults!.option('prefix')!,
+ preset: argResults!.option('preset')!,
uiPath: argResults!.option('ui-path')!,
),
);
diff --git a/packages/remix_cli/lib/src/installer.dart b/packages/remix_cli/lib/src/installer.dart
index 7a3ec0bc8..35afe4ec8 100644
--- a/packages/remix_cli/lib/src/installer.dart
+++ b/packages/remix_cli/lib/src/installer.dart
@@ -21,7 +21,10 @@ $managedExportsStart
$managedExportsEnd
''';
-typedef RegistryLoader = Future Function();
+typedef RegistryLoader = Future Function(String preset);
+
+Future _loadBundledRegistry(String preset) =>
+ RegistryCatalog.loadBundled(preset: preset);
abstract interface class ProjectFileWriter {
void write(File target, String contents);
@@ -54,7 +57,7 @@ final class Installer {
required LineWriter writeOut,
ProcessRunner processRunner = const SystemProcessRunner(),
ProjectFileWriter fileWriter = const AtomicProjectFileWriter(),
- RegistryLoader registryLoader = RegistryCatalog.loadBundled,
+ RegistryLoader registryLoader = _loadBundledRegistry,
}) : projectRoot = projectRoot.absolute,
_writeOut = writeOut,
_processRunner = processRunner,
@@ -73,6 +76,7 @@ final class Installer {
final requested = ProjectConfig.create(
packageRoot: root,
prefix: options.prefix,
+ preset: options.preset,
uiPath: options.uiPath,
);
final configFile = File(p.join(root.path, projectConfigFileName));
@@ -88,9 +92,11 @@ final class Installer {
packageRoot: root,
);
if (existing.prefix != requested.prefix ||
+ existing.preset != requested.preset ||
existing.uiPath != requested.uiPath) {
throw const FormatException(
- 'Existing remix.yaml does not match the requested prefix and UI path.',
+ 'Existing remix.yaml does not match the requested prefix, preset, '
+ 'and UI path.',
);
}
writeConfig = false;
@@ -137,7 +143,7 @@ final class Installer {
final currentBarrel = barrel.readAsStringSync();
validateManagedBarrel(currentBarrel);
- final catalog = await _registryLoader();
+ final catalog = await _registryLoader(config.preset);
final items = catalog.resolve(options.item);
final requested = items.last;
final rendered = {};
diff --git a/packages/remix_cli/lib/src/project_config.dart b/packages/remix_cli/lib/src/project_config.dart
index 2f29f13ff..452c24315 100644
--- a/packages/remix_cli/lib/src/project_config.dart
+++ b/packages/remix_cli/lib/src/project_config.dart
@@ -4,20 +4,28 @@ import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
+import 'registry.dart';
+
const projectConfigFileName = 'remix.yaml';
-const supportedProjectSchema = 1;
+const supportedProjectSchema = 2;
final class ProjectConfig {
- ProjectConfig._({required this.prefix, required this.uiPath});
+ ProjectConfig._({
+ required this.prefix,
+ required this.preset,
+ required this.uiPath,
+ });
factory ProjectConfig.create({
required Directory packageRoot,
required String prefix,
+ required String preset,
required String uiPath,
}) {
_validatePrefix(prefix);
+ _validatePreset(preset);
_validateUiPath(packageRoot, uiPath);
- return ProjectConfig._(prefix: prefix, uiPath: uiPath);
+ return ProjectConfig._(prefix: prefix, preset: preset, uiPath: uiPath);
}
factory ProjectConfig.parse(String source, {required Directory packageRoot}) {
@@ -30,18 +38,28 @@ final class ProjectConfig {
if (document is! YamlMap) {
throw const FormatException('$projectConfigFileName must contain a map.');
}
- _requireExactKeys(document, {'schema', 'prefix', 'paths'}, 'configuration');
-
final schema = document['schema'];
- if (schema != supportedProjectSchema) {
+ if (schema != 1 && schema != supportedProjectSchema) {
throw FormatException(
- 'Unsupported remix.yaml schema $schema; remix_cli supports schema 1.',
+ 'Unsupported remix.yaml schema $schema; '
+ 'remix_cli supports schemas 1 and $supportedProjectSchema.',
);
}
+ _requireExactKeys(
+ document,
+ schema == 1
+ ? {'schema', 'prefix', 'paths'}
+ : {'schema', 'prefix', 'preset', 'paths'},
+ 'configuration',
+ );
final prefix = document['prefix'];
if (prefix is! String) {
throw const FormatException('remix.yaml prefix must be a string.');
}
+ final preset = schema == 1 ? 'default' : document['preset'];
+ if (preset is! String) {
+ throw const FormatException('remix.yaml preset must be a string.');
+ }
final paths = document['paths'];
if (paths is! YamlMap) {
throw const FormatException('remix.yaml paths must be a map.');
@@ -54,19 +72,22 @@ final class ProjectConfig {
return ProjectConfig.create(
packageRoot: packageRoot,
prefix: prefix,
+ preset: preset,
uiPath: uiPath,
);
}
final String prefix;
+ final String preset;
final String uiPath;
String get valuePrefix =>
'${prefix.substring(0, 1).toLowerCase()}${prefix.substring(1)}';
String encode() =>
- '''schema: 1
+ '''schema: 2
prefix: $prefix
+preset: $preset
paths:
ui: ${_encodeYamlPath(uiPath)}
''';
@@ -197,6 +218,17 @@ void _validatePrefix(String prefix) {
}
}
+void _validatePreset(String preset) {
+ if (!RegExp(r'^[a-z][a-z0-9_]*$').hasMatch(preset)) {
+ throw const FormatException('preset must be a lowercase ASCII identifier.');
+ }
+ if (!bundledPresets.contains(preset)) {
+ throw FormatException(
+ 'Unknown preset $preset. Bundled presets: ${bundledPresets.join(', ')}.',
+ );
+ }
+}
+
void _validateUiPath(Directory packageRoot, String uiPath) {
_validateProjectPath(packageRoot, uiPath, _ProjectPathKind.directory);
if (!uiPath.startsWith('lib/')) {
diff --git a/packages/remix_cli/lib/src/registry.dart b/packages/remix_cli/lib/src/registry.dart
index 39052ed75..946421a96 100644
--- a/packages/remix_cli/lib/src/registry.dart
+++ b/packages/remix_cli/lib/src/registry.dart
@@ -5,7 +5,7 @@ import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
-const bundledRegistryUri = 'package:remix_cli/src/registry/registry.yaml';
+const bundledPresets = {'default', 'fortal'};
abstract interface class RegistryAssetLoader {
Future read(Uri uri);
@@ -29,6 +29,7 @@ final class PackageRegistryAssetLoader implements RegistryAssetLoader {
final class RegistryCatalog {
RegistryCatalog._({
+ required this.preset,
required this.items,
required Uri rootUri,
required RegistryAssetLoader loader,
@@ -37,6 +38,7 @@ final class RegistryCatalog {
factory RegistryCatalog.parse(
String source, {
+ required String preset,
required Uri rootUri,
RegistryAssetLoader loader = const PackageRegistryAssetLoader(),
}) {
@@ -63,6 +65,7 @@ final class RegistryCatalog {
items[name] = _parseItem(name, entry.value.value);
}
final catalog = RegistryCatalog._(
+ preset: preset,
items: Map.unmodifiable(items),
rootUri: rootUri,
loader: loader,
@@ -72,24 +75,37 @@ final class RegistryCatalog {
}
static Future loadBundled({
+ required String preset,
RegistryAssetLoader loader = const PackageRegistryAssetLoader(),
}) async {
- final uri = Uri.parse(bundledRegistryUri);
+ if (!bundledPresets.contains(preset)) {
+ throw FormatException(
+ 'Unknown preset $preset. Bundled presets: '
+ '${bundledPresets.join(', ')}.',
+ );
+ }
+ final uri = Uri.parse(
+ 'package:remix_cli/src/registry/$preset/registry.yaml',
+ );
final source = await loader.read(uri);
return RegistryCatalog.parse(
source,
+ preset: preset,
rootUri: uri.resolve('.'),
loader: loader,
);
}
+ final String preset;
final Map items;
final Uri _rootUri;
final RegistryAssetLoader _loader;
List resolve(String requested) {
if (!items.containsKey(requested)) {
- throw FormatException('Unknown registry item $requested.');
+ throw FormatException(
+ 'Unknown registry item $requested in the $preset preset.',
+ );
}
final ordered = [];
final visited = {};
diff --git a/packages/remix_cli/lib/src/registry/registry.yaml b/packages/remix_cli/lib/src/registry/default/registry.yaml
similarity index 99%
rename from packages/remix_cli/lib/src/registry/registry.yaml
rename to packages/remix_cli/lib/src/registry/default/registry.yaml
index 8576679a8..ba575c453 100644
--- a/packages/remix_cli/lib/src/registry/registry.yaml
+++ b/packages/remix_cli/lib/src/registry/default/registry.yaml
@@ -2,7 +2,7 @@ schema: 1
items:
theme:
dependencies:
- remix: ^1.0.0-beta.8
+ remix: ^1.0.0-beta.9
files:
- source: templates/theme/tokens.dart.tmpl
target: "@ui/theme/tokens.dart"
diff --git a/packages/remix_cli/lib/src/registry/templates/accordion/accordion.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/accordion/accordion.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/accordion/accordion.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/accordion/accordion.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/avatar/avatar.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/avatar/avatar.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/avatar/avatar.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/avatar/avatar.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/badge/badge.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/badge/badge.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/badge/badge.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/badge/badge.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/button/button.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/button/button.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/button/button.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/button/button.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/callout/callout.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/callout/callout.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/callout/callout.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/callout/callout.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/card/card.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/card/card.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/card/card.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/card/card.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/chart/chart.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/chart/chart.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/chart/chart.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/chart/chart.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/checkbox/checkbox.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/checkbox/checkbox.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/checkbox/checkbox.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/checkbox/checkbox.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/data_list/data_list.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/data_list/data_list.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/data_list/data_list.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/data_list/data_list.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/data_table/data_table.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/data_table/data_table.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/data_table/data_table.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/data_table/data_table.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/dialog/dialog.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/dialog/dialog.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/dialog/dialog.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/dialog/dialog.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/disclosure/disclosure.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/disclosure/disclosure.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/disclosure/disclosure.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/disclosure/disclosure.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/divider/divider.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/divider/divider.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/divider/divider.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/divider/divider.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/icon_button/icon_button.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/icon_button/icon_button.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/icon_button/icon_button.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/icon_button/icon_button.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/icons/icons.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/icons/icons.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/icons/icons.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/icons/icons.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/link/link.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/link/link.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/link/link.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/link/link.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/menu/menu.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/menu/menu.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/menu/menu.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/menu/menu.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/popover/popover.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/popover/popover.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/popover/popover.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/popover/popover.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/progress/progress.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/progress/progress.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/progress/progress.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/progress/progress.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/radio/radio.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/radio/radio.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/radio/radio.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/radio/radio.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/segmented_control/segmented_control.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/segmented_control/segmented_control.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/segmented_control/segmented_control.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/segmented_control/segmented_control.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/select/select.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/select/select.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/select/select.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/select/select.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/skeleton/skeleton.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/skeleton/skeleton.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/skeleton/skeleton.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/skeleton/skeleton.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/slider/slider.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/slider/slider.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/slider/slider.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/slider/slider.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/spinner/spinner.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/spinner/spinner.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/spinner/spinner.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/spinner/spinner.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/switch/switch.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/switch/switch.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/switch/switch.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/switch/switch.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/tabs/tabs.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/tabs/tabs.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/tabs/tabs.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/tabs/tabs.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/textfield/textfield.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/textfield/textfield.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/textfield/textfield.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/textfield/textfield.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/theme/theme_data.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/theme/theme_data.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/theme/theme_data.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/theme/theme_data.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/theme/theme_scope.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/theme/theme_scope.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/theme/theme_scope.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/theme/theme_scope.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/theme/tokens.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/theme/tokens.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/theme/tokens.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/theme/tokens.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/toggle/toggle.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/toggle/toggle.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/toggle/toggle.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/toggle/toggle.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/toggle_group/toggle_group.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/toggle_group/toggle_group.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/toggle_group/toggle_group.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/toggle_group/toggle_group.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/templates/tooltip/tooltip.dart.tmpl b/packages/remix_cli/lib/src/registry/default/templates/tooltip/tooltip.dart.tmpl
similarity index 100%
rename from packages/remix_cli/lib/src/registry/templates/tooltip/tooltip.dart.tmpl
rename to packages/remix_cli/lib/src/registry/default/templates/tooltip/tooltip.dart.tmpl
diff --git a/packages/remix_cli/lib/src/registry/fortal/registry.yaml b/packages/remix_cli/lib/src/registry/fortal/registry.yaml
new file mode 100644
index 000000000..3319d1bcf
--- /dev/null
+++ b/packages/remix_cli/lib/src/registry/fortal/registry.yaml
@@ -0,0 +1,599 @@
+# Generated by tool/build_fortal_preset.dart. Do not edit.
+schema: 1
+items:
+ theme:
+ dependencies:
+ remix: ^1.0.0-beta.9
+ files:
+ - source: templates/theme/computed.dart.tmpl
+ target: "@ui/theme/computed.dart"
+ - source: templates/theme/control_styles.dart.tmpl
+ target: "@ui/theme/control_styles.dart"
+ - source: templates/theme/radix_colors.dart.tmpl
+ target: "@ui/theme/radix_colors.dart"
+ - source: templates/theme/surface_frame.dart.tmpl
+ target: "@ui/theme/surface_frame.dart"
+ - source: templates/theme/theme.dart.tmpl
+ target: "@ui/theme/theme.dart"
+ - source: templates/theme/theme_data.dart.tmpl
+ target: "@ui/theme/theme_data.dart"
+ - source: templates/theme/theme_scope.dart.tmpl
+ target: "@ui/theme/theme_scope.dart"
+ - source: templates/theme/tokens.dart.tmpl
+ target: "@ui/theme/tokens.dart"
+ exports:
+ - theme/theme.dart
+
+ icons:
+ registryDependencies:
+ - theme
+ dependencies:
+ remix_ui_icons: ^0.1.0
+ files:
+ - source: templates/icons/icons.dart.tmpl
+ target: "@ui/icons.dart"
+ exports:
+ - icons.dart
+
+ accordion:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/accordion/accordion.dart.tmpl
+ target: "@ui/components/accordion.dart"
+ generated:
+ - "@ui/components/accordion.g.dart"
+ exports:
+ - components/accordion.dart
+
+ avatar:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/avatar/avatar.dart.tmpl
+ target: "@ui/components/avatar.dart"
+ generated:
+ - "@ui/components/avatar.g.dart"
+ exports:
+ - components/avatar.dart
+
+ badge:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/badge/badge.dart.tmpl
+ target: "@ui/components/badge.dart"
+ generated:
+ - "@ui/components/badge.g.dart"
+ exports:
+ - components/badge.dart
+
+ base_button:
+ registryDependencies:
+ - theme
+ files:
+ - source: templates/base_button/base_button.dart.tmpl
+ target: "@ui/components/base_button.dart"
+ exports:
+ - components/base_button.dart
+
+ button:
+ registryDependencies:
+ - theme
+ - base_button
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/button/button.dart.tmpl
+ target: "@ui/components/button.dart"
+ generated:
+ - "@ui/components/button.g.dart"
+ exports:
+ - components/button.dart
+
+ callout:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/callout/callout.dart.tmpl
+ target: "@ui/components/callout.dart"
+ generated:
+ - "@ui/components/callout.g.dart"
+ exports:
+ - components/callout.dart
+
+ card:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/card/card.dart.tmpl
+ target: "@ui/components/card.dart"
+ generated:
+ - "@ui/components/card.g.dart"
+ exports:
+ - components/card.dart
+
+ chart:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ mix_chart: ^0.0.1-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/chart/chart.dart.tmpl
+ target: "@ui/components/chart.dart"
+ generated:
+ - "@ui/components/chart.g.dart"
+ exports:
+ - components/chart.dart
+
+ checkbox:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/checkbox/checkbox.dart.tmpl
+ target: "@ui/components/checkbox.dart"
+ generated:
+ - "@ui/components/checkbox.g.dart"
+ exports:
+ - components/checkbox.dart
+
+ code:
+ registryDependencies:
+ - theme
+ - typography
+ files:
+ - source: templates/code/code.dart.tmpl
+ target: "@ui/components/code.dart"
+ exports:
+ - components/code.dart
+
+ data_list:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/data_list/data_list.dart.tmpl
+ target: "@ui/components/data_list.dart"
+ generated:
+ - "@ui/components/data_list.g.dart"
+ exports:
+ - components/data_list.dart
+
+ data_table:
+ registryDependencies:
+ - theme
+ - checkbox
+ - icon_button
+ - select
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/data_table/data_table.dart.tmpl
+ target: "@ui/components/data_table.dart"
+ generated:
+ - "@ui/components/data_table.g.dart"
+ exports:
+ - components/data_table.dart
+
+ dialog:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/dialog/dialog.dart.tmpl
+ target: "@ui/components/dialog.dart"
+ generated:
+ - "@ui/components/dialog.g.dart"
+ exports:
+ - components/dialog.dart
+
+ disclosure:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/disclosure/disclosure.dart.tmpl
+ target: "@ui/components/disclosure.dart"
+ generated:
+ - "@ui/components/disclosure.g.dart"
+ exports:
+ - components/disclosure.dart
+
+ divider:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/divider/divider.dart.tmpl
+ target: "@ui/components/divider.dart"
+ generated:
+ - "@ui/components/divider.g.dart"
+ exports:
+ - components/divider.dart
+
+ heading:
+ registryDependencies:
+ - theme
+ - typography
+ files:
+ - source: templates/heading/heading.dart.tmpl
+ target: "@ui/components/heading.dart"
+ exports:
+ - components/heading.dart
+
+ icon_button:
+ registryDependencies:
+ - theme
+ - base_button
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/icon_button/icon_button.dart.tmpl
+ target: "@ui/components/icon_button.dart"
+ generated:
+ - "@ui/components/icon_button.g.dart"
+ exports:
+ - components/icon_button.dart
+
+ kbd:
+ registryDependencies:
+ - theme
+ - typography
+ files:
+ - source: templates/kbd/kbd.dart.tmpl
+ target: "@ui/components/kbd.dart"
+ exports:
+ - components/kbd.dart
+
+ link:
+ registryDependencies:
+ - theme
+ - typography
+ files:
+ - source: templates/link/link.dart.tmpl
+ target: "@ui/components/link.dart"
+ exports:
+ - components/link.dart
+
+ menu:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/menu/menu.dart.tmpl
+ target: "@ui/components/menu.dart"
+ generated:
+ - "@ui/components/menu.g.dart"
+ exports:
+ - components/menu.dart
+
+ popover:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/popover/popover.dart.tmpl
+ target: "@ui/components/popover.dart"
+ generated:
+ - "@ui/components/popover.g.dart"
+ exports:
+ - components/popover.dart
+
+ progress:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/progress/progress.dart.tmpl
+ target: "@ui/components/progress.dart"
+ generated:
+ - "@ui/components/progress.g.dart"
+ exports:
+ - components/progress.dart
+
+ radio:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/radio/radio.dart.tmpl
+ target: "@ui/components/radio.dart"
+ generated:
+ - "@ui/components/radio.g.dart"
+ exports:
+ - components/radio.dart
+
+ segmented_control:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/segmented_control/segmented_control.dart.tmpl
+ target: "@ui/components/segmented_control.dart"
+ generated:
+ - "@ui/components/segmented_control.g.dart"
+ exports:
+ - components/segmented_control.dart
+
+ select:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/select/select.dart.tmpl
+ target: "@ui/components/select.dart"
+ generated:
+ - "@ui/components/select.g.dart"
+ exports:
+ - components/select.dart
+
+ sidebar:
+ registryDependencies:
+ - theme
+ - text
+ - toggle
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/sidebar/sidebar.dart.tmpl
+ target: "@ui/components/sidebar.dart"
+ generated:
+ - "@ui/components/sidebar.g.dart"
+ exports:
+ - components/sidebar.dart
+
+ skeleton:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/skeleton/skeleton.dart.tmpl
+ target: "@ui/components/skeleton.dart"
+ generated:
+ - "@ui/components/skeleton.g.dart"
+ exports:
+ - components/skeleton.dart
+
+ slider:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/slider/slider.dart.tmpl
+ target: "@ui/components/slider.dart"
+ generated:
+ - "@ui/components/slider.g.dart"
+ exports:
+ - components/slider.dart
+
+ spinner:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/spinner/spinner.dart.tmpl
+ target: "@ui/components/spinner.dart"
+ generated:
+ - "@ui/components/spinner.g.dart"
+ exports:
+ - components/spinner.dart
+
+ switch:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/switch/switch.dart.tmpl
+ target: "@ui/components/switch.dart"
+ generated:
+ - "@ui/components/switch.g.dart"
+ exports:
+ - components/switch.dart
+
+ tabs:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/tabs/tabs.dart.tmpl
+ target: "@ui/components/tabs.dart"
+ generated:
+ - "@ui/components/tabs.g.dart"
+ exports:
+ - components/tabs.dart
+
+ text:
+ registryDependencies:
+ - theme
+ - typography
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/text/text.dart.tmpl
+ target: "@ui/components/text.dart"
+ generated:
+ - "@ui/components/text.g.dart"
+ exports:
+ - components/text.dart
+
+ textfield:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/textfield/textfield.dart.tmpl
+ target: "@ui/components/textfield.dart"
+ generated:
+ - "@ui/components/textfield.g.dart"
+ exports:
+ - components/textfield.dart
+
+ toggle:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/toggle/toggle.dart.tmpl
+ target: "@ui/components/toggle.dart"
+ generated:
+ - "@ui/components/toggle.g.dart"
+ exports:
+ - components/toggle.dart
+
+ toggle_group:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/toggle_group/toggle_group.dart.tmpl
+ target: "@ui/components/toggle_group.dart"
+ generated:
+ - "@ui/components/toggle_group.g.dart"
+ exports:
+ - components/toggle_group.dart
+
+ tooltip:
+ registryDependencies:
+ - theme
+ dependencies:
+ mix_annotations: ^2.2.0-beta.1
+ devDependencies:
+ build_runner: ^2.10.1
+ mix_generator: ^2.2.0-beta.3
+ files:
+ - source: templates/tooltip/tooltip.dart.tmpl
+ target: "@ui/components/tooltip.dart"
+ generated:
+ - "@ui/components/tooltip.g.dart"
+ exports:
+ - components/tooltip.dart
+
+ typography:
+ registryDependencies:
+ - theme
+ files:
+ - source: templates/typography/typography.dart.tmpl
+ target: "@ui/components/typography.dart"
+ exports:
+ - components/typography.dart
diff --git a/packages/remix_cli/lib/src/registry/fortal/templates/accordion/accordion.dart.tmpl b/packages/remix_cli/lib/src/registry/fortal/templates/accordion/accordion.dart.tmpl
new file mode 100644
index 000000000..5597f9f7c
--- /dev/null
+++ b/packages/remix_cli/lib/src/registry/fortal/templates/accordion/accordion.dart.tmpl
@@ -0,0 +1,166 @@
+import 'package:flutter/widgets.dart';
+import 'package:mix_annotations/mix_annotations.dart';
+import 'package:remix/remix.dart';
+
+import '../theme/theme.dart';
+
+part 'accordion.g.dart';
+
+/// {{typePrefix}} accordion size presets.
+enum {{typePrefix}}AccordionSize { size1, size2, size3 }
+
+/// {{typePrefix}} accordion color variants.
+enum {{typePrefix}}AccordionVariant { surface, soft }
+
+/// {{typePrefix}}-themed preset for [RemixAccordion].
+@MixWidget(target: RemixAccordion.new)
+AccordionStyler {{valuePrefix}}AccordionStyle({
+ {{typePrefix}}AccordionVariant variant = .surface,
+ {{typePrefix}}AccordionSize size = .size2,
+ AccordionStyler style = const AccordionStyler.create(),
+}) {
+ return (switch (variant) {
+ .surface => _{{valuePrefix}}AccordionSurfaceStyler(size),
+ .soft => _{{valuePrefix}}AccordionSoftStyler(size),
+ }).merge(style);
+}
+
+// Panel anatomy follows the mapped Table family (see data_table.dart):
+// `container` alone owns radius, frame, fill, and clipping, while trigger and
+// content stay flat rectangles that simply get cropped to its rounded shape.
+// The frame and divider are foreground borders so edge-to-edge child fills
+// cannot partially cover their antialiased edges.
+AccordionStyler _{{valuePrefix}}AccordionBaseStyler({{typePrefix}}AccordionSize size) {
+ return AccordionStyler()
+ .trigger(.direction(.horizontal))
+ .leadingIcon(.color({{typePrefix}}Tokens.gray11()))
+ .title(
+ .fontWeight(
+ {{typePrefix}}Tokens.fontWeightMedium(),
+ ).color({{typePrefix}}Tokens.gray12()),
+ )
+ .trailingIcon(.color({{typePrefix}}Tokens.gray11()))
+ .content(.width(.infinity))
+ .merge(_{{valuePrefix}}AccordionSizeStyler(size));
+}
+
+AccordionStyler _{{valuePrefix}}AccordionFocusStyler() {
+ return AccordionStyler().trigger(FlexBoxStyler().{{valuePrefix}}FocusRing());
+}
+
+AccordionStyler _{{valuePrefix}}AccordionDisabledStyler() {
+ return AccordionStyler()
+ .trigger(.color({{typePrefix}}Tokens.grayA3()))
+ .leadingIcon(.color({{typePrefix}}Tokens.gray8()))
+ .title(.color({{typePrefix}}Tokens.gray8()))
+ .trailingIcon(.color({{typePrefix}}Tokens.gray8()));
+}
+
+AccordionStyler _{{valuePrefix}}AccordionSurfaceStyler([
+ {{typePrefix}}AccordionSize size = .size2,
+]) {
+ return _{{valuePrefix}}AccordionBaseStyler(size)
+ .container(
+ {{valuePrefix}}SurfaceFrame(
+ fillColor: {{typePrefix}}Tokens.gray2(),
+ borderColor: {{typePrefix}}Tokens.gray6(),
+ borderWidth: {{typePrefix}}Tokens.borderWidth1(),
+ radius: _{{valuePrefix}}AccordionRadius(size),
+ ),
+ )
+ .trigger(.color({{typePrefix}}Tokens.gray1()))
+ .content(
+ BoxStyler()
+ .foregroundDecoration(
+ BoxDecorationMix(
+ border: BoxBorderMix.top(
+ _{{valuePrefix}}AccordionBorderSide({{typePrefix}}Tokens.gray6()),
+ ),
+ ),
+ )
+ .wrap(_{{valuePrefix}}AccordionContentTypography({{typePrefix}}Tokens.gray12())),
+ )
+ .onHovered(.trigger(.color({{typePrefix}}Tokens.gray2())))
+ .onPressed(.trigger(.color({{typePrefix}}Tokens.gray3())))
+ .onFocusVisible(_{{valuePrefix}}AccordionFocusStyler())
+ .onDisabled(_{{valuePrefix}}AccordionDisabledStyler());
+}
+
+AccordionStyler _{{valuePrefix}}AccordionSoftStyler([
+ {{typePrefix}}AccordionSize size = .size2,
+]) {
+ return _{{valuePrefix}}AccordionBaseStyler(size)
+ .container(
+ {{valuePrefix}}SurfaceFrame(
+ fillColor: {{typePrefix}}Tokens.accent2(),
+ borderColor: {{typePrefix}}Tokens.accent6(),
+ borderWidth: {{typePrefix}}Tokens.borderWidth1(),
+ radius: _{{valuePrefix}}AccordionRadius(size),
+ ),
+ )
+ .trigger(.color({{typePrefix}}Tokens.accent2()))
+ .title(.color({{typePrefix}}Tokens.accent12()))
+ .trailingIcon(.color({{typePrefix}}Tokens.accent11()))
+ .content(
+ BoxStyler()
+ .foregroundDecoration(
+ BoxDecorationMix(
+ border: BoxBorderMix.top(
+ _{{valuePrefix}}AccordionBorderSide({{typePrefix}}Tokens.accent6()),
+ ),
+ ),
+ )
+ .wrap(_{{valuePrefix}}AccordionContentTypography({{typePrefix}}Tokens.accent12())),
+ )
+ .onHovered(.trigger(.color({{typePrefix}}Tokens.accent3())))
+ .onPressed(.trigger(.color({{typePrefix}}Tokens.accent4())))
+ .onFocusVisible(_{{valuePrefix}}AccordionFocusStyler())
+ .onDisabled(_{{valuePrefix}}AccordionDisabledStyler());
+}
+
+/// The 1px edge shared by the panel's outer border and the trigger/content
+/// divider, so the seam reads as a continuation of the frame rather than an
+/// unrelated line.
+BorderSideMix _{{valuePrefix}}AccordionBorderSide(Color color) =>
+ BorderSideMix(color: color, width: {{typePrefix}}Tokens.borderWidth1());
+
+/// Pins bare [Text] accordion content to the 14px type-scale step (`text2`)
+/// regardless of accordion size, so content never renders larger than its own
+/// trigger's title (measured 14/15/16px at size1/size2/size3). {{typePrefix}} text
+/// children pin their own run. [color] supplies the variant's own content tint.
+WidgetModifierConfig _{{valuePrefix}}AccordionContentTypography(Color color) =>
+ WidgetModifierConfig.defaultTextStyle(
+ style: {{typePrefix}}Tokens.text2.mix(),
+ ).defaultTextStyle(style: TextStyleMix().color(color));
+
+AccordionStyler _{{valuePrefix}}AccordionSizeStyler({{typePrefix}}AccordionSize size) {
+ return switch (size) {
+ .size1 => AccordionStyler(
+ trigger: FlexBoxStyler().padding(.all({{typePrefix}}Tokens.space2())),
+ leadingIcon: .size({{typePrefix}}Tokens.space4()),
+ title: .style({{typePrefix}}Tokens.text2.mix()),
+ trailingIcon: .size({{typePrefix}}Tokens.space4()),
+ content: .padding(.all({{typePrefix}}Tokens.space2())),
+ ),
+ .size2 => AccordionStyler(
+ trigger: FlexBoxStyler().padding(.all({{typePrefix}}Tokens.space3())),
+ leadingIcon: .size({{typePrefix}}Tokens.spinnerSize3()),
+ title: .style({{typePrefix}}Tokens.accordionText2.mix()),
+ trailingIcon: .size({{typePrefix}}Tokens.spinnerSize3()),
+ content: .padding(.all({{typePrefix}}Tokens.space3())),
+ ),
+ .size3 => AccordionStyler(
+ trigger: FlexBoxStyler().padding(.all({{typePrefix}}Tokens.space4())),
+ leadingIcon: .size({{typePrefix}}Tokens.space5()),
+ title: .style({{typePrefix}}Tokens.text3.mix()),
+ trailingIcon: .size({{typePrefix}}Tokens.space5()),
+ content: .padding(.all({{typePrefix}}Tokens.space4())),
+ ),
+ };
+}
+
+Radius _{{valuePrefix}}AccordionRadius({{typePrefix}}AccordionSize size) => switch (size) {
+ .size1 => {{typePrefix}}Tokens.radius3(),
+ .size2 => {{typePrefix}}Tokens.radius4(),
+ .size3 => {{typePrefix}}Tokens.radius5(),
+};
diff --git a/packages/remix_cli/lib/src/registry/fortal/templates/avatar/avatar.dart.tmpl b/packages/remix_cli/lib/src/registry/fortal/templates/avatar/avatar.dart.tmpl
new file mode 100644
index 000000000..db22521b0
--- /dev/null
+++ b/packages/remix_cli/lib/src/registry/fortal/templates/avatar/avatar.dart.tmpl
@@ -0,0 +1,127 @@
+import 'package:flutter/widgets.dart';
+import 'package:mix_annotations/mix_annotations.dart';
+import 'package:remix/remix.dart';
+
+import '../theme/theme.dart';
+
+part 'avatar.g.dart';
+
+/// Radix Themes Avatar size presets.
+enum {{typePrefix}}AvatarSize {
+ size1,
+ size2,
+ size3,
+ size4,
+ size5,
+ size6,
+ size7,
+ size8,
+ size9,
+}
+
+/// Radix Themes Avatar variants.
+enum {{typePrefix}}AvatarVariant { soft, solid }
+
+/// {{typePrefix}}-themed Avatar with the Radix size, variant, and override contract.
+///
+/// [fallbackLength] selects the pinned one- or two-character fallback
+/// typography. Pass `2` when [RemixAvatar.label] contains two initials.
+@MixWidget(target: RemixAvatar.new)
+AvatarStyler {{valuePrefix}}AvatarStyle({
+ {{typePrefix}}AvatarVariant variant = .soft,
+ {{typePrefix}}AvatarSize size = .size3,
+ bool highContrast = false,
+ int fallbackLength = 1,
+ AvatarStyler style = const AvatarStyler.create(),
+}) {
+ final base = _{{valuePrefix}}AvatarBaseStyler(size, fallbackLength: fallbackLength);
+ final softContent = highContrast
+ ? {{typePrefix}}Tokens.accent12()
+ : {{typePrefix}}Tokens.accentA11();
+ final solidContent = highContrast
+ ? {{typePrefix}}Tokens.accent1()
+ : {{typePrefix}}Tokens.accentContrast();
+ return (switch (variant) {
+ .soft =>
+ base
+ .color({{typePrefix}}Tokens.accentA3())
+ .labelColor(softContent)
+ .iconColor(softContent),
+ .solid =>
+ base
+ .color(
+ highContrast ? {{typePrefix}}Tokens.accent12() : {{typePrefix}}Tokens.accent9(),
+ )
+ .labelColor(solidContent)
+ .iconColor(solidContent),
+ }).merge(style);
+}
+
+AvatarStyler _{{valuePrefix}}AvatarBaseStyler(
+ {{typePrefix}}AvatarSize size, {
+ required int fallbackLength,
+}) {
+ final fallbackText = _{{valuePrefix}}AvatarFallbackText(size, fallbackLength);
+ final dimension = _{{valuePrefix}}AvatarDimension(size);
+ return AvatarStyler()
+ .clipBehavior(.hardEdge)
+ .label(
+ TextStyler(
+ style: fallbackText.mix(),
+ ).fontWeight({{typePrefix}}Tokens.fontWeightMedium()),
+ )
+ .icon(.size(_{{valuePrefix}}AvatarIconSize(size)).color({{typePrefix}}Tokens.accentA11()))
+ .size(dimension, dimension)
+ .borderRadius(.all(_{{valuePrefix}}AvatarRadius(size)));
+}
+
+double _{{valuePrefix}}AvatarDimension({{typePrefix}}AvatarSize size) => switch (size) {
+ .size1 => {{typePrefix}}Tokens.space5(),
+ .size2 => {{typePrefix}}Tokens.space6(),
+ .size3 => {{typePrefix}}Tokens.space7(),
+ .size4 => {{typePrefix}}Tokens.space8(),
+ .size5 => {{typePrefix}}Tokens.space9(),
+ .size6 => {{typePrefix}}Tokens.avatarSize6(),
+ .size7 => {{typePrefix}}Tokens.avatarSize7(),
+ .size8 => {{typePrefix}}Tokens.avatarSize8(),
+ .size9 => {{typePrefix}}Tokens.avatarSize9(),
+};
+
+double _{{valuePrefix}}AvatarIconSize({{typePrefix}}AvatarSize size) => switch (size) {
+ .size1 => {{typePrefix}}Tokens.avatarIconSize1(),
+ .size2 => {{typePrefix}}Tokens.avatarIconSize2(),
+ .size3 => {{typePrefix}}Tokens.avatarIconSize3(),
+ .size4 => {{typePrefix}}Tokens.avatarIconSize4(),
+ .size5 => {{typePrefix}}Tokens.avatarIconSize5(),
+ .size6 => {{typePrefix}}Tokens.avatarIconSize6(),
+ .size7 => {{typePrefix}}Tokens.avatarIconSize7(),
+ .size8 => {{typePrefix}}Tokens.avatarIconSize8(),
+ .size9 => {{typePrefix}}Tokens.avatarIconSize9(),
+};
+
+Radius _{{valuePrefix}}AvatarRadius({{typePrefix}}AvatarSize size) => switch (size) {
+ .size1 || .size2 => {{typePrefix}}Tokens.radius2OrFull(),
+ .size3 || .size4 => {{typePrefix}}Tokens.radius3OrFull(),
+ .size5 => {{typePrefix}}Tokens.radius4OrFull(),
+ .size6 || .size7 => {{typePrefix}}Tokens.radius5OrFull(),
+ .size8 || .size9 => {{typePrefix}}Tokens.radius6OrFull(),
+};
+
+TextStyleToken _{{valuePrefix}}AvatarFallbackText(
+ {{typePrefix}}AvatarSize size,
+ int fallbackLength,
+) => switch ((size, fallbackLength == 2)) {
+ (.size1, false) => {{typePrefix}}Tokens.avatarFallback1One,
+ (.size1, true) => {{typePrefix}}Tokens.avatarFallback1Two,
+ (.size2, false) => {{typePrefix}}Tokens.avatarFallback2One,
+ (.size2, true) => {{typePrefix}}Tokens.avatarFallback2Two,
+ (.size3, false) => {{typePrefix}}Tokens.avatarFallback3One,
+ (.size3, true) => {{typePrefix}}Tokens.avatarFallback3Two,
+ (.size4, false) => {{typePrefix}}Tokens.avatarFallback4One,
+ (.size4, true) => {{typePrefix}}Tokens.avatarFallback4Two,
+ (.size5, _) => {{typePrefix}}Tokens.avatarFallback5,
+ (.size6, _) => {{typePrefix}}Tokens.avatarFallback6,
+ (.size7, _) => {{typePrefix}}Tokens.avatarFallback7,
+ (.size8, _) => {{typePrefix}}Tokens.avatarFallback8,
+ (.size9, _) => {{typePrefix}}Tokens.avatarFallback9,
+};
diff --git a/packages/remix_cli/lib/src/registry/fortal/templates/badge/badge.dart.tmpl b/packages/remix_cli/lib/src/registry/fortal/templates/badge/badge.dart.tmpl
new file mode 100644
index 000000000..1ee04fc76
--- /dev/null
+++ b/packages/remix_cli/lib/src/registry/fortal/templates/badge/badge.dart.tmpl
@@ -0,0 +1,108 @@
+import 'package:flutter/widgets.dart';
+import 'package:mix_annotations/mix_annotations.dart';
+import 'package:remix/remix.dart';
+
+import '../theme/theme.dart';
+
+part 'badge.g.dart';
+
+/// Radix Themes Badge size presets.
+enum {{typePrefix}}BadgeSize { size1, size2, size3 }
+
+/// Radix Themes Badge variants.
+enum {{typePrefix}}BadgeVariant { solid, soft, surface, outline }
+
+/// {{typePrefix}}-themed Badge with the Radix size, variant, and override contract.
+@MixWidget(target: RemixBadge.new)
+BadgeStyler {{valuePrefix}}BadgeStyle({
+ {{typePrefix}}BadgeVariant variant = .soft,
+ {{typePrefix}}BadgeSize size = .size1,
+ bool highContrast = false,
+ BadgeStyler style = const BadgeStyler.create(),
+}) {
+ final base = _{{valuePrefix}}BadgeBaseStyler(size);
+ return (switch (variant) {
+ .solid =>
+ base
+ .color(
+ highContrast ? {{typePrefix}}Tokens.accent12() : {{typePrefix}}Tokens.accent9(),
+ )
+ .labelColor(
+ highContrast
+ ? {{typePrefix}}Tokens.accent1()
+ : {{typePrefix}}Tokens.accentContrast(),
+ ),
+ .soft =>
+ base
+ .color({{typePrefix}}Tokens.accentA3())
+ .labelColor(
+ // Step 11 is Radix low-contrast text, not WCAG AA 4.5:1 on
+ // accentA3 over colorPanelSolid. highContrast promotes accent12.
+ highContrast ? {{typePrefix}}Tokens.accent12() : {{typePrefix}}Tokens.accentA11(),
+ ),
+ .surface =>
+ base
+ .color({{typePrefix}}Tokens.accentSurface())
+ .containerEffects(
+ RemixBoxEffectsMix.behindContent(
+ {{valuePrefix}}InsetSurface(strokes: [{{typePrefix}}Tokens.accentA6()]),
+ ),
+ )
+ .labelColor(
+ highContrast ? {{typePrefix}}Tokens.accent12() : {{typePrefix}}Tokens.accentA11(),
+ ),
+ .outline =>
+ base
+ .containerEffects(
+ RemixBoxEffectsMix.behindContent(
+ {{valuePrefix}}InsetSurface(
+ strokes: [
+ highContrast
+ ? {{typePrefix}}Tokens.accentA7()
+ : {{typePrefix}}Tokens.accentA8(),
+ if (highContrast) {{typePrefix}}Tokens.grayA11(),
+ ],
+ ),
+ ),
+ )
+ .labelColor(
+ highContrast ? {{typePrefix}}Tokens.accent12() : {{typePrefix}}Tokens.accentA11(),
+ ),
+ }).merge(style);
+}
+
+BadgeStyler _{{valuePrefix}}BadgeBaseStyler({{typePrefix}}BadgeSize size) {
+ final radius = _{{valuePrefix}}BadgeRadius(size);
+ return BadgeStyler(
+ container: .padding(_{{valuePrefix}}BadgePadding(size)),
+ label: .style(
+ _{{valuePrefix}}BadgeText(size).mix(),
+ ).fontWeight({{typePrefix}}Tokens.fontWeightMedium()),
+ ).borderRadius(.all(radius));
+}
+
+TextStyleToken _{{valuePrefix}}BadgeText({{typePrefix}}BadgeSize size) => switch (size) {
+ .size1 || .size2 => {{typePrefix}}Tokens.text1,
+ .size3 => {{typePrefix}}Tokens.text2,
+};
+
+EdgeInsetsGeometryMix _{{valuePrefix}}BadgePadding({{typePrefix}}BadgeSize size) =>
+ switch (size) {
+ .size1 => EdgeInsetsGeometryMix.symmetric(
+ horizontal: {{typePrefix}}Tokens.badgePaddingX1(),
+ vertical: {{typePrefix}}Tokens.badgePaddingY1(),
+ ),
+ .size2 => EdgeInsetsGeometryMix.symmetric(
+ horizontal: {{typePrefix}}Tokens.space2(),
+ vertical: {{typePrefix}}Tokens.space1(),
+ ),
+ .size3 => EdgeInsetsGeometryMix.symmetric(
+ horizontal: {{typePrefix}}Tokens.badgePaddingX3(),
+ vertical: {{typePrefix}}Tokens.space1(),
+ ),
+ };
+
+Radius _{{valuePrefix}}BadgeRadius({{typePrefix}}BadgeSize size) => switch (size) {
+ .size1 => {{typePrefix}}Tokens.radius1OrFull(),
+ .size2 || .size3 => {{typePrefix}}Tokens.radius2OrFull(),
+};
diff --git a/packages/remix_cli/lib/src/registry/fortal/templates/base_button/base_button.dart.tmpl b/packages/remix_cli/lib/src/registry/fortal/templates/base_button/base_button.dart.tmpl
new file mode 100644
index 000000000..d79b330d8
--- /dev/null
+++ b/packages/remix_cli/lib/src/registry/fortal/templates/base_button/base_button.dart.tmpl
@@ -0,0 +1,547 @@
+import 'package:flutter/widgets.dart';
+import 'package:remix/remix.dart';
+
+import '../theme/theme.dart';
+
+/// The Radix BaseButton size scale shared by Button and IconButton.
+///
+/// Deliberate: this mirrors [{{typePrefix}}BaseButtonVariant]. Button and IconButton
+/// each own a size enum, so the shared metrics need a common type — passing the
+/// raw `size.index + 1` instead would drop every switch below to a wildcard and
+/// defer an unknown size to a runtime throw.
+enum {{typePrefix}}BaseButtonSize { size1, size2, size3, size4 }
+
+/// Shared Radix BaseButton metrics used by Button and IconButton recipes.
+({
+ double height,
+ double paddingX,
+ double gap,
+ Radius radius,
+ TextStyleToken text,
+ double spinnerSize,
+})
+{{valuePrefix}}BaseButtonMetrics({{typePrefix}}BaseButtonSize size) => switch (size) {
+ .size1 => (
+ height: {{typePrefix}}Tokens.space5(),
+ paddingX: {{typePrefix}}Tokens.space2(),
+ gap: {{typePrefix}}Tokens.space1(),
+ radius: {{typePrefix}}Tokens.radius1OrFull(),
+ text: {{typePrefix}}Tokens.text1,
+ spinnerSize: {{typePrefix}}Tokens.space3(),
+ ),
+ .size2 => (
+ height: {{typePrefix}}Tokens.space6(),
+ paddingX: {{typePrefix}}Tokens.space3(),
+ gap: {{typePrefix}}Tokens.space2(),
+ radius: {{typePrefix}}Tokens.radius2OrFull(),
+ text: {{typePrefix}}Tokens.text2,
+ spinnerSize: {{typePrefix}}Tokens.space4(),
+ ),
+ .size3 => (
+ height: {{typePrefix}}Tokens.space7(),
+ paddingX: {{typePrefix}}Tokens.space4(),
+ gap: {{typePrefix}}Tokens.space3(),
+ radius: {{typePrefix}}Tokens.radius3OrFull(),
+ text: {{typePrefix}}Tokens.text3,
+ spinnerSize: {{typePrefix}}Tokens.space4(),
+ ),
+ .size4 => (
+ height: {{typePrefix}}Tokens.space8(),
+ paddingX: {{typePrefix}}Tokens.space5(),
+ gap: {{typePrefix}}Tokens.space3(),
+ radius: {{typePrefix}}Tokens.radius4OrFull(),
+ text: {{typePrefix}}Tokens.text4,
+ spinnerSize: {{typePrefix}}Tokens.spinnerSize3(),
+ ),
+};
+
+/// Content-box metrics for the ghost BaseButton variant.
+({double paddingX, double paddingY, double marginX, double marginY, double gap})
+{{valuePrefix}}BaseButtonGhostMetrics({{typePrefix}}BaseButtonSize size) => switch (size) {
+ .size1 => (
+ paddingX: {{typePrefix}}Tokens.space2(),
+ paddingY: {{typePrefix}}Tokens.space1(),
+ marginX: {{typePrefix}}Tokens.baseButtonGhostMarginX12(),
+ marginY: {{typePrefix}}Tokens.baseButtonGhostMarginY12(),
+ gap: {{typePrefix}}Tokens.space1(),
+ ),
+ .size2 => (
+ paddingX: {{typePrefix}}Tokens.space2(),
+ paddingY: {{typePrefix}}Tokens.space1(),
+ marginX: {{typePrefix}}Tokens.baseButtonGhostMarginX12(),
+ marginY: {{typePrefix}}Tokens.baseButtonGhostMarginY12(),
+ gap: {{typePrefix}}Tokens.space1(),
+ ),
+ .size3 => (
+ paddingX: {{typePrefix}}Tokens.space3(),
+ paddingY: {{typePrefix}}Tokens.baseButtonGhostPaddingY3(),
+ marginX: {{typePrefix}}Tokens.baseButtonGhostMarginX3(),
+ marginY: {{typePrefix}}Tokens.baseButtonGhostMarginY3(),
+ gap: {{typePrefix}}Tokens.space2(),
+ ),
+ .size4 => (
+ paddingX: {{typePrefix}}Tokens.space4(),
+ paddingY: {{typePrefix}}Tokens.space2(),
+ marginX: {{typePrefix}}Tokens.baseButtonGhostMarginX4(),
+ marginY: {{typePrefix}}Tokens.baseButtonGhostMarginY4(),
+ gap: {{typePrefix}}Tokens.space2(),
+ ),
+};
+
+/// Content-box metrics for the ghost IconButton variant.
+({double padding, double margin}) {{valuePrefix}}IconButtonGhostMetrics(
+ {{typePrefix}}BaseButtonSize size,
+) => switch (size) {
+ .size1 => (
+ padding: {{typePrefix}}Tokens.space1(),
+ margin: {{typePrefix}}Tokens.iconButtonGhostMargin1(),
+ ),
+ .size2 => (
+ padding: {{typePrefix}}Tokens.iconButtonGhostPadding2(),
+ margin: {{typePrefix}}Tokens.iconButtonGhostMargin2(),
+ ),
+ .size3 => (
+ padding: {{typePrefix}}Tokens.space2(),
+ margin: {{typePrefix}}Tokens.iconButtonGhostMargin3(),
+ ),
+ .size4 => (
+ padding: {{typePrefix}}Tokens.space3(),
+ margin: {{typePrefix}}Tokens.iconButtonGhostMargin4(),
+ ),
+};
+
+/// Resolves a mode-aware ordered CSS filter at the component build context.
+WidgetModifierConfig {{valuePrefix}}ModeAwareFilter({
+ required List light,
+ required List dark,
+}) => WidgetModifierConfig.modifier(
+ _{{typePrefix}}ModeAwareFilterMix(light: light, dark: dark),
+);
+
+/// Explicit identity filter used to clear a higher-priority state filter.
+WidgetModifierConfig {{valuePrefix}}ClearFilter() =>
+ {{valuePrefix}}ModeAwareFilter(light: const [], dark: const []);
+
+/// Exact classic BaseButton surface for a visual state.
+RemixBoxEffectLayerMix {{valuePrefix}}ClassicBaseButtonSurface({
+ required bool highContrast,
+ bool hovered = false,
+ bool pressed = false,
+ bool disabled = false,
+}) {
+ final inset = {{typePrefix}}Tokens.baseButtonClassicAfterInset();
+ if (disabled) {
+ return RemixBoxEffectLayerMix(
+ gradients: [
+ RemixLinearGradientMix(
+ colors: [
+ {{typePrefix}}Tokens.blackA1(),
+ const Color(0x00000000),
+ {{typePrefix}}Tokens.whiteA1(),
+ ],
+ stops: const [-0.2, 0.4, 1],
+ ),
+ RemixLinearGradientMix(
+ colors: [{{typePrefix}}Tokens.grayA2(), {{typePrefix}}Tokens.grayA2()],
+ ),
+ ],
+ gradientInsets: [inset, inset],
+ shadowToken: {{typePrefix}}Tokens.baseButtonClassicDisabledShadows,
+ );
+ }
+
+ final baseColor = highContrast
+ ? {{typePrefix}}Tokens.accent12()
+ : {{typePrefix}}Tokens.accent9();
+ final afterColor = hovered && !highContrast
+ ? {{typePrefix}}Tokens.accent10()
+ : baseColor;
+ final pseudoGradient = RemixLinearGradientMix(
+ colors: [
+ highContrast
+ ? hovered || pressed
+ ? {{typePrefix}}Tokens.blackA5()
+ : {{typePrefix}}Tokens.blackA3()
+ : hovered
+ ? {{typePrefix}}Tokens.blackA2()
+ : pressed
+ ? {{typePrefix}}Tokens.blackA2()
+ : {{typePrefix}}Tokens.blackA1(),
+ const Color(0x00000000),
+ highContrast
+ ? pressed
+ ? {{typePrefix}}Tokens.whiteA3()
+ : {{typePrefix}}Tokens.whiteA2()
+ : hovered || pressed
+ ? {{typePrefix}}Tokens.whiteA3()
+ : {{typePrefix}}Tokens.whiteA2(),
+ ],
+ stops: hovered && !highContrast
+ ? const [-0.15, 0.425, 1]
+ : const [0, 0.5, 1],
+ );
+ final gradients = [
+ pseudoGradient,
+ RemixLinearGradientMix(colors: [afterColor, afterColor]),
+ if (pressed)
+ RemixLinearGradientMix(
+ colors: [{{typePrefix}}Tokens.blackA1(), const Color(0x00000000)],
+ )
+ else ...[
+ RemixLinearGradientMix(
+ colors: [
+ const Color(0x00000000),
+ const Color(0x00000000),
+ {{typePrefix}}Tokens.grayA4(),
+ {{typePrefix}}Tokens.grayA4(),
+ ],
+ stops: const [0, 0.5, 0.5, 1],
+ ),
+ RemixLinearGradientMix(
+ colors: [
+ const Color(0x00000000),
+ const Color(0x00000000),
+ baseColor,
+ baseColor,
+ ],
+ stops: const [0, 0.5, 0.8, 1],
+ ),
+ ],
+ ];
+ return RemixBoxEffectLayerMix(
+ gradients: gradients,
+ gradientInsets: [inset, inset, ...List.filled(gradients.length - 2, 0)],
+ shadowToken: pressed
+ ? highContrast
+ ? {{typePrefix}}Tokens.baseButtonClassicActiveHighContrastShadows
+ : {{typePrefix}}Tokens.baseButtonClassicActiveShadows
+ : highContrast
+ ? {{typePrefix}}Tokens.baseButtonClassicHighContrastShadows
+ : {{typePrefix}}Tokens.baseButtonClassicShadows,
+ );
+}
+
+final class _{{typePrefix}}ModeAwareFilterMix
+ extends ModifierMix {
+ const _{{typePrefix}}ModeAwareFilterMix({required this.light, required this.dark});
+
+ final List light;
+ final List dark;
+
+ @override
+ RemixOrderedColorFilterModifier resolve(BuildContext context) =>
+ RemixOrderedColorFilterModifier(
+ {{typePrefix}}Theme.of(context).isDark ? dark : light,
+ );
+
+ @override
+ _{{typePrefix}}ModeAwareFilterMix merge(_{{typePrefix}}ModeAwareFilterMix? other) =>
+ other ?? this;
+
+ @override
+ List