remix 3.0.0-beta.4 → 3.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +4 -2
  2. package/dist/assets/types/hmr.d.ts +2 -0
  3. package/dist/cli-entry.js +1 -1
  4. package/dist/data-table/cli.d.ts +2 -0
  5. package/dist/data-table/cli.d.ts.map +1 -0
  6. package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
  7. package/dist/node-hmr/runtime.d.ts +2 -0
  8. package/dist/node-hmr/runtime.d.ts.map +1 -0
  9. package/dist/node-hmr/runtime.js +2 -0
  10. package/dist/node-hmr/types.d.ts +2 -0
  11. package/dist/node-hmr.d.ts +2 -0
  12. package/dist/node-hmr.d.ts.map +1 -0
  13. package/dist/{ui/glyph.js → node-hmr.js} +1 -1
  14. package/dist/ui/accordion/primitives.d.ts +2 -0
  15. package/dist/ui/accordion/primitives.d.ts.map +1 -0
  16. package/dist/ui/accordion/primitives.js +2 -0
  17. package/dist/ui/button.d.ts +1 -0
  18. package/dist/ui/button.d.ts.map +1 -1
  19. package/dist/ui/button.js +1 -0
  20. package/dist/ui/checkbox.d.ts +3 -0
  21. package/dist/ui/checkbox.d.ts.map +1 -0
  22. package/dist/ui/checkbox.js +3 -0
  23. package/dist/ui/combobox/primitives.d.ts +2 -0
  24. package/dist/ui/combobox/primitives.d.ts.map +1 -0
  25. package/dist/ui/combobox/primitives.js +2 -0
  26. package/dist/ui/dev/refresh.d.ts +2 -0
  27. package/dist/ui/dev/refresh.d.ts.map +1 -0
  28. package/dist/ui/dev/refresh.js +2 -0
  29. package/dist/ui/input.d.ts +3 -0
  30. package/dist/ui/input.d.ts.map +1 -0
  31. package/dist/ui/input.js +3 -0
  32. package/dist/ui/menu/primitives.d.ts +2 -0
  33. package/dist/ui/menu/primitives.d.ts.map +1 -0
  34. package/dist/ui/menu/primitives.js +2 -0
  35. package/dist/ui/radio.d.ts +3 -0
  36. package/dist/ui/radio.d.ts.map +1 -0
  37. package/dist/ui/radio.js +3 -0
  38. package/dist/ui/select/primitives.d.ts +2 -0
  39. package/dist/ui/select/primitives.d.ts.map +1 -0
  40. package/dist/ui/select/primitives.js +2 -0
  41. package/dist/ui/tabs/primitives.d.ts +2 -0
  42. package/dist/ui/tabs/primitives.d.ts.map +1 -0
  43. package/dist/ui/tabs/primitives.js +2 -0
  44. package/dist/ui/tabs.d.ts +2 -0
  45. package/dist/ui/tabs.d.ts.map +1 -0
  46. package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
  47. package/dist/ui/toggle/primitives.d.ts +2 -0
  48. package/dist/ui/toggle/primitives.d.ts.map +1 -0
  49. package/dist/ui/toggle/primitives.js +2 -0
  50. package/dist/ui/toggle.d.ts +3 -0
  51. package/dist/ui/toggle.d.ts.map +1 -0
  52. package/dist/ui/toggle.js +3 -0
  53. package/dist/ui-hmr/assets.d.ts +2 -0
  54. package/dist/ui-hmr/assets.d.ts.map +1 -0
  55. package/dist/ui-hmr/assets.js +2 -0
  56. package/dist/ui-hmr/node.d.ts +3 -0
  57. package/dist/ui-hmr/node.d.ts.map +1 -0
  58. package/dist/ui-hmr/node.js +3 -0
  59. package/dist/ui-hmr/runtime/browser.d.ts +2 -0
  60. package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
  61. package/dist/ui-hmr/runtime/browser.js +2 -0
  62. package/dist/ui-hmr/runtime/server.d.ts +2 -0
  63. package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
  64. package/dist/ui-hmr/runtime/server.js +2 -0
  65. package/dist/ui-hmr.d.ts +2 -0
  66. package/dist/ui-hmr.d.ts.map +1 -0
  67. package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
  68. package/package.json +122 -142
  69. package/src/assets/README.md +322 -56
  70. package/src/assets/types/hmr.d.ts +2 -0
  71. package/src/cli/README.md +105 -1
  72. package/src/cookie/README.md +4 -4
  73. package/src/data-table/README.md +202 -68
  74. package/src/data-table/cli.ts +2 -0
  75. package/src/data-table-mysql/README.md +46 -17
  76. package/src/data-table-postgres/README.md +39 -13
  77. package/src/data-table-sqlite/README.md +38 -20
  78. package/src/fetch-proxy/README.md +25 -0
  79. package/src/form-data-parser/README.md +4 -4
  80. package/src/mime/README.md +8 -1
  81. package/src/node-fetch-server/README.md +39 -13
  82. package/src/node-hmr/README.md +307 -0
  83. package/src/node-hmr/runtime.ts +2 -0
  84. package/src/node-hmr/types.d.ts +2 -0
  85. package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
  86. package/src/route-pattern/README.md +141 -13
  87. package/src/session/README.md +1 -1
  88. package/src/session-middleware/README.md +9 -7
  89. package/src/test/README.md +161 -115
  90. package/src/ui/README.md +116 -157
  91. package/src/ui/accordion/README.md +50 -14
  92. package/src/ui/accordion/primitives/README.md +202 -0
  93. package/src/ui/accordion/primitives.ts +2 -0
  94. package/src/ui/anchor/README.md +37 -2
  95. package/src/ui/breadcrumbs/README.md +4 -4
  96. package/src/ui/button/README.md +26 -26
  97. package/src/ui/button.ts +1 -0
  98. package/src/ui/checkbox/README.md +59 -0
  99. package/src/ui/checkbox.ts +3 -0
  100. package/src/ui/combobox/README.md +58 -9
  101. package/src/ui/combobox/primitives/README.md +194 -0
  102. package/src/ui/combobox/primitives.ts +2 -0
  103. package/src/ui/dev/refresh.ts +2 -0
  104. package/src/ui/input/README.md +52 -0
  105. package/src/ui/input.ts +3 -0
  106. package/src/ui/listbox/README.md +9 -41
  107. package/src/ui/menu/README.md +55 -14
  108. package/src/ui/menu/primitives/README.md +161 -0
  109. package/src/ui/menu/primitives.ts +2 -0
  110. package/src/ui/popover/README.md +20 -39
  111. package/src/ui/radio/README.md +53 -0
  112. package/src/ui/radio.ts +3 -0
  113. package/src/ui/select/README.md +29 -19
  114. package/src/ui/select/primitives/README.md +117 -0
  115. package/src/ui/select/primitives.ts +2 -0
  116. package/src/ui/tabs/README.md +141 -0
  117. package/src/ui/tabs/primitives/README.md +141 -0
  118. package/src/ui/tabs/primitives.ts +2 -0
  119. package/src/ui/tabs.ts +2 -0
  120. package/src/ui/test/README.md +151 -60
  121. package/src/ui/toggle/README.md +56 -0
  122. package/src/ui/toggle/primitives/README.md +56 -0
  123. package/src/ui/toggle/primitives.ts +2 -0
  124. package/src/ui/toggle.ts +3 -0
  125. package/src/ui-hmr/README.md +119 -0
  126. package/{dist/ui/separator.js → src/ui-hmr/assets.ts} +1 -1
  127. package/src/ui-hmr/node.ts +3 -0
  128. package/src/ui-hmr/runtime/browser.ts +2 -0
  129. package/src/ui-hmr/runtime/server.ts +2 -0
  130. package/src/ui-hmr.ts +2 -0
  131. package/dist/ui/glyph.d.ts +0 -2
  132. package/dist/ui/glyph.d.ts.map +0 -1
  133. package/dist/ui/scroll-lock.d.ts +0 -2
  134. package/dist/ui/scroll-lock.d.ts.map +0 -1
  135. package/dist/ui/separator.d.ts +0 -2
  136. package/dist/ui/separator.d.ts.map +0 -1
  137. package/dist/ui/theme.d.ts +0 -2
  138. package/dist/ui/theme.d.ts.map +0 -1
  139. package/src/ui/glyph/README.md +0 -72
  140. package/src/ui/scroll-lock/README.md +0 -33
  141. package/src/ui/scroll-lock.ts +0 -2
  142. package/src/ui/separator.ts +0 -2
  143. package/src/ui/theme/README.md +0 -103
@@ -12,7 +12,7 @@ A test framework for JavaScript and TypeScript projects.
12
12
  - Per-test and hook timeouts with `t.signal` abort support
13
13
  - Unified code coverage reporting across unit and E2E tests
14
14
  - Watch mode
15
- - Config file support (`remix-test.config.ts`)
15
+ - Static CLI configuration through `remix.json`
16
16
 
17
17
  ## Installation
18
18
 
@@ -42,127 +42,145 @@ Run tests with the CLI:
42
42
  remix test
43
43
  ```
44
44
 
45
- By default, `remix test` discovers all files matching `**/*.test{,.e2e}.{ts,tsx}`. Pass one or more globs as positional arguments to override:
45
+ By default, `remix test` discovers all files matching `**/*.test{,.browser,.e2e}.{ts,tsx}`. Pass one or more globs as positional arguments to override:
46
46
 
47
47
  ```sh
48
48
  remix test "src/**/*.test.ts"
49
49
  remix test "src/**/*.test.ts" "tests/**/*.test.tsx"
50
50
  ```
51
51
 
52
- Or, you may control via the `glob.test` config field/CLI arg. Each `glob.*` field accepts a single string or an array of patterns, and `--glob.*` flags can be repeated on the CLI.
52
+ You may also repeat the `--glob.*` flags. Positional globs take precedence over `--glob.test`.
53
53
 
54
54
  ### Config File
55
55
 
56
- Create a `remix-test.config.ts` (or `.js`) file at the root of your project (shown with default values):
56
+ Create an optional `remix.json` at the root of your project. It is parsed as JSONC, so comments and
57
+ trailing commas are allowed:
58
+
59
+ ```jsonc
60
+ {
61
+ "$schema": "https://remix.run/schemas/remix.json",
62
+ "test": {
63
+ "files": ["**/*.test{,.browser,.e2e}.{ts,tsx}"],
64
+ "browserFiles": ["**/*.test.browser.{ts,tsx}"],
65
+ "e2eFiles": ["**/*.test.e2e.{ts,tsx}"],
66
+ "exclude": ["node_modules/**", "dist/**"],
67
+ "type": ["server", "browser", "e2e"],
68
+ "only": ["/checkout/i"],
69
+
70
+ "concurrency": 2,
71
+ "pool": "forks",
72
+ "setup": "./test/setup.ts",
73
+ "watch": false,
74
+
75
+ "playwright": {
76
+ "echo": false,
77
+ "open": false,
78
+ "configFile": "./playwright.config.ts",
79
+ "projects": ["chromium", "firefox"],
80
+ },
57
81
 
58
- ```ts
59
- import type { RemixTestConfig } from 'remix/test'
60
-
61
- export default {
62
- // Browser options for E2E tests
63
- browser: {
64
- // Echo browser console output to the terminal
65
- echo: false,
66
- // Open browser (via playwright `headless:false`) and keep it open after tests
67
- // complete (useful for debugging)
68
- open: false,
82
+ "reporter": "spec",
83
+ "quiet": false,
84
+
85
+ "coverage": {
86
+ "enabled": true,
87
+ "dir": ".coverage",
88
+ "include": ["src/**"],
89
+ "exclude": ["src/**/*.test.ts"],
90
+ "statements": 80,
91
+ "lines": 80,
92
+ "branches": 80,
93
+ "functions": 80,
94
+ },
69
95
  },
96
+ }
97
+ ```
70
98
 
71
- // Max number of concurrent test workers (default `os.availableParallelism()`)
72
- concurrency: 2,
73
-
74
- // Pool for server and E2E test files ("forks", "threads")
75
- pool: 'forks',
76
-
77
- // Code coverage options
78
- coverage: {
79
- // Enable coverage reporting
80
- enabled: true,
81
- // Output directory (default: ".coverage")
82
- dir: '.coverage',
83
- // Glob pattern(s) to include/exclude
84
- include: 'src/**',
85
- exclude: 'src/**/*.test.ts',
86
- // Minimum thresholds (%)
87
- statements: 80,
88
- lines: 80,
89
- branches: 80,
90
- functions: 80,
91
- },
99
+ Every field is optional. Relative paths and globs resolve from the directory containing the config
100
+ file. Explicit CLI flags and positional globs take precedence. Repeatable flags replace configured
101
+ arrays, and nested Playwright and coverage values merge by field.
92
102
 
93
- // Glob pattern(s) identifying test files
94
- glob: {
95
- // All test files (default: "**/*.test{,.browser,.e2e}.{ts,tsx}").
96
- test: '**/*.test{,.browser,.e2e}.ts',
97
- // Browser test files (default: "**/*.test.browser.{ts,tsx}")
98
- browser: '**/*.test.browser.ts',
99
- // E2E test files (default: "**/*.test.e2e.{ts,tsx}")
100
- e2e: '**/*.test.e2e.ts',
101
- },
103
+ `remix-test.config.ts` and `remix-test.config.js` are no longer discovered. Move their static values
104
+ under `remix.json#test`. Move inline Playwright configuration into `playwright.config.ts` and point
105
+ `playwright.configFile` at it. Use slash-delimited strings for regular expressions, and use CLI flags
106
+ or package scripts for environment-specific overrides.
102
107
 
103
- // Playwright configuration for E2E tests, or string path to an existing
104
- // config file on disk
105
- playwrightConfig: {
106
- projects: [
107
- { name: 'chromium', use: { browserName: 'chromium' } },
108
- { name: 'firefox', use: { browserName: 'firefox' } },
109
- ],
110
- use: {
111
- navigationTimeout: 5_000,
112
- actionTimeout: 5_000,
113
- },
114
- },
108
+ ### CLI Options
115
109
 
116
- // Playwright project(s) to run E2E tests for
117
- project: 'chromium',
110
+ Use the global `--config` flag to select another Remix config file. The flag path resolves from the
111
+ current working directory:
118
112
 
119
- // Test reporter ("spec", "files", "tap", "dot")
120
- reporter: 'spec',
113
+ ```sh
114
+ remix test --config ./config/remix.ci.json
115
+ ```
121
116
 
122
- // Path to a setup module (see Setup section below)
123
- setup: './test/setup.ts',
117
+ You may specify any test setting as a CLI flag. Boolean settings have negative forms so configured
118
+ `true` values can be disabled explicitly:
119
+
120
+ | Flag | Short |
121
+ | --------------------------- | ----- |
122
+ | `--browser.echo` | |
123
+ | `--no-browser.echo` | |
124
+ | `--browser.open` | |
125
+ | `--no-browser.open` | |
126
+ | `--concurrency <n>` | `-c` |
127
+ | `--coverage` | |
128
+ | `--no-coverage` | |
129
+ | `--coverage.dir <path>` | |
130
+ | `--coverage.include` | |
131
+ | `--coverage.exclude` | |
132
+ | `--coverage.statements` | |
133
+ | `--coverage.lines` | |
134
+ | `--coverage.branches` | |
135
+ | `--coverage.functions` | |
136
+ | `--glob.test` | |
137
+ | `--glob.browser` | |
138
+ | `--glob.e2e` | |
139
+ | `--glob.exclude` | |
140
+ | `--playwrightConfig <path>` | |
141
+ | `--only <pattern>` | |
142
+ | `--pool <forks\|threads>` | |
143
+ | `--project <name>` | `-p` |
144
+ | `--quiet` | `-q` |
145
+ | `--no-quiet` | |
146
+ | `--reporter <name>` | `-r` |
147
+ | `--setup <path>` | `-s` |
148
+ | `--type <name>` | `-t` |
149
+ | `--watch` | `-w` |
150
+ | `--no-watch` | |
151
+
152
+ The standalone `remix-test` executable is no longer installed. Update scripts to use the main Remix CLI:
153
+
154
+ ```diff
155
+ - "test": "remix-test --type server"
156
+ + "test": "remix test --type server"
157
+ ```
124
158
 
125
- // Test type(s) to run ("server", "browser", "e2e")
126
- type: ['server', 'browser', 'e2e'],
159
+ ### Focusing Tests
127
160
 
128
- // Watch for file changes and re-run
129
- watch: false,
130
- } satisfies RemixTestConfig
131
- ```
161
+ Use `.only` to focus a suite or test while developing:
132
162
 
133
- ### CLI Options
163
+ ```ts
164
+ describe.only('Cart routes', () => {
165
+ it('loads cart items', () => {})
166
+ })
134
167
 
135
- You can point to a different config file location with the `--config` flag:
168
+ describe('Checkout routes', () => {
169
+ it.only('redirects anonymous users', () => {})
170
+ })
171
+ ```
172
+
173
+ Use `--only <pattern>` to focus tests from the CLI without editing source. Plain patterns are case-insensitive JavaScript regular expressions matched against suite names and full test names:
136
174
 
137
175
  ```sh
138
- remix test --config ./tests/config.ts
176
+ remix test --only 'Cart routes'
177
+ remix test --only 'Checkout routes > redirects anonymous users'
178
+ remix test --only '/anonymous users$/'
139
179
  ```
140
180
 
141
- You may also specify any config field as a CLI flag which will take precedence over config file values:
142
-
143
- | Flag | Short |
144
- | --------------------------- | --------- | --- |
145
- | `--browser.echo` | |
146
- | `--browser.open` | |
147
- | `--concurrency <n>` | `-c` |
148
- | `--coverage` | |
149
- | `--coverage.dir <path>` | |
150
- | `--coverage.include` | |
151
- | `--coverage.exclude` | |
152
- | `--coverage.statements` | |
153
- | `--coverage.lines` | |
154
- | `--coverage.branches` | |
155
- | `--coverage.functions` | |
156
- | `--glob.test` | |
157
- | `--glob.browser` | |
158
- | `--glob.e2e` | |
159
- | `--playwrightConfig <path>` | |
160
- | `--pool <forks | threads>` | |
161
- | `--project <name>` | `-p` |
162
- | `--reporter <name>` | `-r` |
163
- | `--setup <path>` | `-s` |
164
- | `--type <name>` | `-t` |
165
- | `--watch` | `-w` |
181
+ Slash-delimited CLI and `remix.json` patterns preserve their flags, so `/pattern/` is case-sensitive and `/pattern/i` is case-insensitive. Plain string patterns are case-insensitive. Programmatic callers may also pass `RegExp` values.
182
+
183
+ Full test names join nested `describe` names and the test name with `>`. For example, `describe('Cart routes', () => describe('loader', () => it('loads cart items', ...)))` has the full test name `Cart routes > loader > loads cart items`.
166
184
 
167
185
  ### Setup
168
186
 
@@ -221,12 +239,19 @@ suite('My Test Suite', () => {
221
239
  import { runRemixTest } from 'remix/test/cli'
222
240
 
223
241
  let exitCode = await runRemixTest({
224
- argv: ['--type', 'server'],
242
+ concurrency: 1,
225
243
  cwd: process.cwd(),
244
+ glob: { test: 'src/**/*.test.ts' },
245
+ type: ['server'],
226
246
  })
227
247
  ```
228
248
 
229
- `runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls `process.exit()` with that code when the run finishes so open workers, browsers, or project handles cannot keep the CLI alive.
249
+ The programmatic runner accepts structured options only; it does not discover or load configuration
250
+ files. Programmatic callers may pass richer values such as an inline Playwright config or `RegExp`
251
+ test-name filters. The main Remix CLI owns `remix.json` loading and passes the resolved options to the
252
+ runner.
253
+
254
+ `runRemixTest()` does not read `process.argv` or terminate the process; it returns the runner exit code. The main `remix` executable owns argument parsing and passes the final code to `process.exit()` so open workers, browsers, or project handles cannot keep the CLI alive. `@remix-run/test` does not install a standalone executable.
230
255
 
231
256
  ### Test Context
232
257
 
@@ -426,28 +451,49 @@ describe('checkout', () => {
426
451
  })
427
452
  ```
428
453
 
429
- Configure Playwright (browsers, timeouts, viewport, etc.) via `playwrightConfig` in your config file:
454
+ Configure Playwright's browsers, timeouts, viewport, and other executable settings in
455
+ `playwright.config.ts`:
430
456
 
431
457
  ```ts
432
- export default {
433
- playwrightConfig: {
434
- projects: [
435
- { name: 'chromium', use: { browserName: 'chromium' } },
436
- { name: 'firefox', use: { browserName: 'firefox' } },
437
- { name: 'webkit', use: { browserName: 'webkit' } },
438
- ],
439
- use: {
440
- navigationTimeout: 5_000,
441
- actionTimeout: 5_000,
442
- },
458
+ import { defineConfig } from 'playwright/test'
459
+
460
+ export default defineConfig({
461
+ projects: [
462
+ { name: 'chromium', use: { browserName: 'chromium' } },
463
+ { name: 'firefox', use: { browserName: 'firefox' } },
464
+ { name: 'webkit', use: { browserName: 'webkit' } },
465
+ ],
466
+ use: {
467
+ navigationTimeout: 5_000,
468
+ actionTimeout: 5_000,
443
469
  },
470
+ })
471
+ ```
444
472
 
445
- // Or, point to an existing playwright config file
446
- // playwrightConfig: './playwright.config.ts'
447
- } satisfies RemixTestConfig
473
+ Reference it from `remix.json` when it is not at the default location:
474
+
475
+ ```jsonc
476
+ {
477
+ "test": {
478
+ "playwright": {
479
+ "configFile": "./playwright.config.ts",
480
+ },
481
+ },
482
+ }
448
483
  ```
449
484
 
450
- Set `browser.open: true` to keep the browser open after tests finish — useful for debugging failures.
485
+ Set `test.playwright.open` to `true`, or pass `--browser.open`, to keep the browser open after tests
486
+ finish—useful for debugging failures.
487
+
488
+ ## Related Packages
489
+
490
+ - [`assert`](https://github.com/remix-run/remix/tree/main/packages/assert) provides assertions that work in server and browser tests.
491
+ - [`ui`](https://github.com/remix-run/remix/tree/main/packages/ui) provides the `remix/ui/test` browser rendering utilities.
492
+
493
+ ## Related Work
494
+
495
+ - [Playwright](https://playwright.dev) provides the browser automation used by browser and E2E tests.
496
+ - [Node.js test runner](https://nodejs.org/api/test.html) provides prior art for the test framework API and reporting model.
451
497
 
452
498
  ## License
453
499
 
package/src/ui/README.md CHANGED
@@ -1,15 +1,14 @@
1
1
  # ui
2
2
 
3
- Runtime UI primitives for Remix apps, including the component runtime, server rendering, frame hydration, reusable mixins, first-party components, and theme tokens.
3
+ Runtime UI primitives for Remix apps, including the component runtime, server rendering, frame hydration, reusable mixins, and headless first-party behavior primitives.
4
4
 
5
5
  ## Features
6
6
 
7
- - Component runtime APIs for rendering, hydration, frame navigation, and JSX
7
+ - Component runtime APIs for rendering, hydration, link and form frame navigation, and JSX
8
8
  - Server rendering APIs for streaming Remix UI trees and frames
9
9
  - `mix` composition with event, ref, CSS, and animation helpers
10
- - First-party components such as buttons, menus, listboxes, popovers, and selects
11
- - Fixed typed `theme` contract whose leaves resolve to `var(--rmx-...)`
12
- - `createTheme()` and `createGlyphSheet()` utilities for shared app styling and glyphs
10
+ - Headless behavior primitives for controls such as menus, listboxes, popovers, selects, and comboboxes
11
+ - Lower-level utilities for keyboard events, typeahead search, refs, attributes, and CSS transition timing
13
12
 
14
13
  ## Installation
15
14
 
@@ -19,188 +18,148 @@ npm i remix
19
18
 
20
19
  ## Usage
21
20
 
22
- Define your app theme once:
21
+ Compose behavior primitives with your own markup and styles:
23
22
 
24
23
  ```tsx
25
- import { createTheme } from 'remix/ui'
26
-
27
- let Theme = createTheme({
28
- space: {
29
- none: '0px',
30
- px: '1px',
31
- xs: '2px',
32
- sm: '4px',
33
- md: '8px',
34
- lg: '12px',
35
- xl: '16px',
36
- xxl: '24px',
37
- },
38
- radius: {
39
- none: '0px',
40
- sm: '4px',
41
- md: '8px',
42
- lg: '12px',
43
- xl: '16px',
44
- full: '9999px',
45
- },
46
- fontSize: {
47
- xxxs: '10px',
48
- xxs: '11px',
49
- xs: '12px',
50
- sm: '14px',
51
- md: '16px',
52
- lg: '18px',
53
- xl: '20px',
54
- xxl: '28px',
55
- },
56
- lineHeight: {
57
- tight: '1.2',
58
- normal: '1.5',
59
- relaxed: '1.7',
60
- },
61
- fontWeight: {
62
- normal: '400',
63
- medium: '500',
64
- semibold: '600',
65
- bold: '700',
66
- },
67
- shadow: {
68
- xs: '0 1px 2px rgb(0 0 0 / 0.05)',
69
- sm: '0 1px 3px rgb(0 0 0 / 0.10)',
70
- md: '0 4px 10px rgb(0 0 0 / 0.12)',
71
- lg: '0 10px 30px rgb(0 0 0 / 0.16)',
72
- xl: '0 20px 50px rgb(0 0 0 / 0.20)',
73
- },
74
- zIndex: {
75
- dropdown: '1000',
76
- popover: '1100',
77
- sticky: '1200',
78
- overlay: '1300',
79
- modal: '1400',
80
- toast: '1500',
81
- tooltip: '1600',
82
- },
83
- surface: {
84
- lvl0: '#ffffff',
85
- lvl1: '#f8fafc',
86
- lvl2: '#f1f5f9',
87
- lvl3: '#e5edf7',
88
- lvl4: '#dbe6f4',
89
- },
90
- colors: {
91
- text: {
92
- primary: '#111827',
93
- secondary: '#374151',
94
- muted: '#6b7280',
95
- link: '#2563eb',
96
- },
97
- border: {
98
- subtle: '#e5e7eb',
99
- default: '#d1d5db',
100
- strong: '#9ca3af',
101
- },
102
- focus: {
103
- ring: '#3b82f6',
104
- },
105
- overlay: {
106
- scrim: 'rgb(0 0 0 / 0.45)',
107
- },
108
- action: {
109
- primary: {
110
- background: '#2563eb',
111
- backgroundHover: '#1d4ed8',
112
- backgroundActive: '#1e40af',
113
- foreground: '#ffffff',
114
- border: '#2563eb',
115
- },
116
- secondary: {
117
- background: '#ffffff',
118
- backgroundHover: '#f8fafc',
119
- backgroundActive: '#f1f5f9',
120
- foreground: '#111827',
121
- border: '#d1d5db',
122
- },
123
- danger: {
124
- background: '#dc2626',
125
- backgroundHover: '#b91c1c',
126
- backgroundActive: '#991b1b',
127
- foreground: '#ffffff',
128
- border: '#dc2626',
129
- },
130
- },
131
- },
24
+ import { css } from 'remix/ui'
25
+ import * as popover from 'remix/ui/popover'
26
+
27
+ let triggerCss = css({
28
+ border: '1px solid #d1d5db',
29
+ borderRadius: '6px',
30
+ padding: '6px 10px',
132
31
  })
133
- ```
134
32
 
135
- Render the theme once near the top of your document:
33
+ let surfaceCss = css({
34
+ background: 'white',
35
+ border: '1px solid #d1d5db',
36
+ borderRadius: '6px',
37
+ padding: '8px',
38
+ })
136
39
 
137
- ```tsx
138
- import type { Handle, RemixNode } from 'remix/ui'
40
+ function ViewOptions() {
41
+ let open = false
139
42
 
140
- function Layout(handle: Handle<{ children: RemixNode }>) {
141
43
  return () => (
142
- <html>
143
- <head>
144
- <Theme />
145
- </head>
146
- <body>{handle.props.children}</body>
147
- </html>
44
+ <popover.Context>
45
+ <button
46
+ mix={[triggerCss, popover.anchor({ placement: 'bottom-end' }), popover.focusOnHide()]}
47
+ onClick={() => {
48
+ open = true
49
+ }}
50
+ type="button"
51
+ >
52
+ View options
53
+ </button>
54
+ <div
55
+ mix={[
56
+ surfaceCss,
57
+ popover.surface({
58
+ open,
59
+ onHide() {
60
+ open = false
61
+ },
62
+ }),
63
+ ]}
64
+ >
65
+ Panel content
66
+ </div>
67
+ </popover.Context>
148
68
  )
149
69
  }
150
70
  ```
151
71
 
152
- Consume the shared token contract from app code and first-party components:
72
+ Button styling is available as a composable mixin:
153
73
 
154
74
  ```tsx
155
- import { css } from 'remix/ui'
156
- import { theme } from 'remix/ui'
157
-
158
- let card = css({
159
- backgroundColor: theme.surface.lvl0,
160
- color: theme.colors.text.primary,
161
- border: `1px solid ${theme.colors.border.subtle}`,
162
- borderRadius: theme.radius.md,
163
- paddingInline: theme.space.md,
164
- paddingBlock: theme.space.sm,
75
+ import button from 'remix/ui/button'
76
+
77
+ function Actions() {
78
+ return () => <button mix={button({ tone: 'primary' })}>Create project</button>
79
+ }
80
+ ```
81
+
82
+ ## Frame Navigation
83
+
84
+ Configure `resolveFrame` once to progressively enhance same-origin links and forms. The resolver owns the fetch request and receives native submission metadata for non-GET forms:
85
+
86
+ ```tsx
87
+ import type { ResolveFrameOptions } from 'remix/ui'
88
+ import { run } from 'remix/ui'
89
+
90
+ let app = run({
91
+ async loadModule(moduleUrl, exportName) {
92
+ let mod = await import(moduleUrl)
93
+ return mod[exportName]
94
+ },
95
+ async resolveFrame(src, options) {
96
+ return fetch(src, {
97
+ headers: { Accept: 'text/html', 'X-Remix-Frame': 'true' },
98
+ method: options?.method,
99
+ body: getRequestBody(options),
100
+ signal: options?.signal,
101
+ })
102
+ },
165
103
  })
166
104
 
167
- <div mix={card} />
105
+ function getRequestBody(options?: ResolveFrameOptions): BodyInit | undefined {
106
+ let formData = options?.formData
107
+ if (!formData) return
108
+ if (options.encType !== 'application/x-www-form-urlencoded') return formData
109
+
110
+ let body = new URLSearchParams()
111
+ for (let [name, value] of formData) {
112
+ body.append(name, typeof value === 'string' ? value : value.name)
113
+ }
114
+ return body
115
+ }
116
+
117
+ await app.ready()
168
118
  ```
169
119
 
170
- Render shared glyphs separately from the theme styles:
120
+ Forms remain ordinary HTML forms before the runtime starts. Add `rmx-target` to reload a named frame, or `rmx-document` to require a full-document submission:
171
121
 
172
122
  ```tsx
173
- import type { Handle, RemixNode } from 'remix/ui'
174
- import { Button } from 'remix/ui/button'
175
- import { Glyph } from 'remix/ui/glyph'
176
- import { RMX_01, RMX_01_GLYPHS } from 'remix/ui/theme'
123
+ import { Frame } from 'remix/ui'
177
124
 
178
- function Layout(handle: Handle<{ children: RemixNode }>) {
125
+ function AccountPage() {
179
126
  return () => (
180
- <html>
181
- <head>
182
- <RMX_01 />
183
- </head>
184
- <body>
185
- <RMX_01_GLYPHS />
186
- <Button startIcon={<Glyph name="add" />} tone="primary">
187
- New project
188
- </Button>
189
- {handle.props.children}
190
- </body>
191
- </html>
127
+ <>
128
+ <Frame name="account" src="/account/edit" />
129
+ <form action="/account/edit" method="post" rmx-target="account">
130
+ <label for="display-name">Display name</label>
131
+ <input id="display-name" name="displayName" required />
132
+ <button type="submit">Save</button>
133
+ </form>
134
+ </>
192
135
  )
193
136
  }
194
137
  ```
195
138
 
139
+ Native constraint validation and submitter overrides still apply. GET form values arrive in `src`; non-GET forms provide `formData`, `method`, and `encType` to the resolver. See [Frames](https://github.com/remix-run/remix/blob/main/packages/ui/docs/frames.md#form-navigation) for targeting, history behavior, request encoding, opt-outs, and server response guidance.
140
+
141
+ Use `rmx-history="push|replace"` on an enhanced anchor or form to control how the navigation updates history. This can override the automatic replacement used for non-GET form submissions to the current URL.
142
+
143
+ ## Preserving Client-Owned DOM
144
+
145
+ Use `rmx-preserve-dom` on the smallest element whose live DOM should belong to client code after initial render, such as a custom element or third-party widget:
146
+
147
+ ```tsx
148
+ <pagefind-ui data-key="search" rmx-preserve-dom>
149
+ <button type="button">Search</button>
150
+ </pagefind-ui>
151
+ ```
152
+
153
+ Remix UI still renders the element's children during SSR and still hydrates any initial client entries inside it. On later frame reloads, matched `rmx-preserve-dom` elements keep their current attributes and children instead of accepting incoming DOM updates. See [Preserving client-owned DOM](https://github.com/remix-run/remix/blob/main/packages/ui/docs/frames.md#preserving-client-owned-dom) for guidance and caveats.
154
+
196
155
  ## Cascade Layers
197
156
 
198
- Remix UI emits its built-in theme reset in `rmx-reset` and generated `css(...)` rules under `rmx`. Unlayered CSS outranks layered component CSS, so use explicit layer order when mixing Remix UI with global styles.
157
+ Remix UI emits generated `css(...)` rules under the `rmx` cascade layer. Unlayered CSS outranks layered CSS, so use explicit layer order when mixing Remix UI with global styles.
199
158
 
200
- Put layers that should lose to Remix UI before `rmx-reset` and `rmx`:
159
+ Put layers that should lose to Remix UI before `rmx`:
201
160
 
202
161
  ```css
203
- @layer base, rmx-reset, rmx;
162
+ @layer base, rmx;
204
163
 
205
164
  @layer base {
206
165
  button,