Skip to content

Commit 977e397

Browse files
Refresh Remix alpha 6 agent docs (#73)
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 08763b6 commit 977e397

156 files changed

Lines changed: 5477 additions & 11443 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/remix/SKILL.md

Lines changed: 561 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Animating Elements
2+
3+
## What This Covers
4+
5+
How to animate insertion, removal, and layout changes of elements. Read this
6+
when the task involves:
7+
8+
- Adding entrance, exit, or shared-layout transitions to UI
9+
- Choosing between spring physics (`spring(...)`) and time-based easing
10+
(`tween`)
11+
- Coordinating CSS transitions with the same easing as JS animations
12+
- Imperative animation loops via `requestAnimationFrame`
13+
14+
Import animation APIs from `remix/ui/animation`. For the smaller set of
15+
animation helpers that show up alongside other mixins, see
16+
`mixins-styling-events.md`.
17+
18+
## Animation Mixins
19+
20+
### `animateEntrance(config)`
21+
22+
Animates an element when inserted. Config specifies the **starting** style the
23+
element animates **from**:
24+
25+
```tsx
26+
<div
27+
mix={animateEntrance({
28+
opacity: 0,
29+
transform: 'translateY(8px)',
30+
...spring('smooth'),
31+
})}
32+
/>
33+
```
34+
35+
### `animateExit(config)`
36+
37+
Animates an element when removed. Config specifies the **ending** style the
38+
element animates **to**. The element stays in the DOM until the animation
39+
completes:
40+
41+
```tsx
42+
{
43+
isVisible && (
44+
<div
45+
key="panel"
46+
mix={[
47+
animateEntrance({
48+
opacity: 0,
49+
transform: 'scale(0.98)',
50+
...spring('smooth'),
51+
}),
52+
animateExit({ opacity: 0, duration: 120, easing: 'ease-in' }),
53+
]}
54+
/>
55+
)
56+
}
57+
```
58+
59+
### `animateLayout(config?)`
60+
61+
Animates layout changes (position/size) using FLIP-style transforms:
62+
63+
```tsx
64+
{
65+
items.map((item) => (
66+
<li
67+
key={item.id}
68+
mix={animateLayout({ ...spring({ duration: 500, bounce: 0.2 }) })}
69+
/>
70+
))
71+
}
72+
```
73+
74+
Options: `duration` (default 200ms), `easing` (default spring snappy), `size`
75+
(default true — include scale projection for size changes).
76+
77+
### Combining mixins
78+
79+
```tsx
80+
<div
81+
key="card"
82+
mix={[
83+
animateEntrance({
84+
opacity: 0,
85+
transform: 'scale(0.95)',
86+
...spring('snappy'),
87+
}),
88+
animateExit({
89+
opacity: 0,
90+
transform: 'scale(0.98)',
91+
duration: 120,
92+
easing: 'ease-in',
93+
}),
94+
animateLayout({ duration: 220, easing: 'ease-out' }),
95+
]}
96+
/>
97+
```
98+
99+
### Shared-layout swap
100+
101+
```tsx
102+
<div mix={css({ display: 'grid', '& > *': { gridArea: '1 / 1' } })}>
103+
{stateA ? (
104+
<div
105+
key="a"
106+
mix={[animateEntrance({ opacity: 0 }), animateExit({ opacity: 0 })]}
107+
/>
108+
) : (
109+
<div
110+
key="b"
111+
mix={[animateEntrance({ opacity: 0 }), animateExit({ opacity: 0 })]}
112+
/>
113+
)}
114+
</div>
115+
```
116+
117+
## Spring API
118+
119+
Physics-based spring animation. Returns a `SpringIterator` with `duration`,
120+
`easing`, and `toString()` for CSS.
121+
122+
### Presets
123+
124+
| Preset | Bounce | Duration | Character |
125+
| -------- | ------ | -------- | --------------------------- |
126+
| `smooth` | -0.3 | 400ms | Overdamped, no overshoot |
127+
| `snappy` | 0 | 200ms | Critically damped, quick |
128+
| `bouncy` | 0.3 | 400ms | Underdamped, visible bounce |
129+
130+
```tsx
131+
spring('bouncy')
132+
spring('snappy')
133+
spring('smooth')
134+
spring('bouncy', { duration: 300 }) // override duration
135+
```
136+
137+
### Custom spring
138+
139+
```tsx
140+
spring({ duration: 500, bounce: 0.3 })
141+
spring({ duration: 500, bounce: 0.3, velocity: 2 }) // continue momentum from gesture
142+
```
143+
144+
### Spread into animation mixins
145+
146+
Spreading a spring gives both `duration` and `easing`:
147+
148+
```tsx
149+
animateEntrance({ opacity: 0, ...spring('bouncy') })
150+
```
151+
152+
### CSS transitions
153+
154+
The iterator stringifies to `"550ms linear(...)"`:
155+
156+
```tsx
157+
css({ transition: `width ${spring('bouncy')}` })
158+
```
159+
160+
Or use the `spring.transition()` helper for multiple properties:
161+
162+
```tsx
163+
css({ transition: spring.transition('width', 'bouncy') })
164+
css({ transition: spring.transition(['left', 'top'], 'snappy') })
165+
```
166+
167+
### Web Animations API
168+
169+
```tsx
170+
element.animate(keyframes, { ...spring('bouncy') })
171+
```
172+
173+
### JS iteration
174+
175+
The iterator yields position values from 0 to 1, one per frame:
176+
177+
```tsx
178+
for (let t of spring('bouncy')) {
179+
let x = from + (to - from) * t
180+
updateSomething(x)
181+
await nextFrame()
182+
}
183+
```
184+
185+
## Tween API
186+
187+
Generator-based tween for animating values over time with cubic bezier easing.
188+
Prefer animation mixins or CSS transitions with `spring` for most UI work. Use
189+
`tween` for imperative `requestAnimationFrame` loops, canvas/WebGL, or non-CSS
190+
properties.
191+
192+
```tsx
193+
import { tween, easings } from 'remix/ui/animation'
194+
195+
let animation = tween({
196+
from: 0,
197+
to: 100,
198+
duration: 300,
199+
curve: easings.easeOut,
200+
})
201+
202+
animation.next() // initialize
203+
function tick(timestamp: number) {
204+
if (handle.signal.aborted) return
205+
let { value, done } = animation.next(timestamp)
206+
element.style.transform = `translateX(${value}px)`
207+
if (!done) requestAnimationFrame(tick)
208+
}
209+
requestAnimationFrame(tick)
210+
```
211+
212+
Built-in easings: `easings.linear`, `easings.ease`, `easings.easeIn`,
213+
`easings.easeOut`, `easings.easeInOut`.
214+
215+
## Practical Guidance
216+
217+
- Always key conditional or switching elements you expect to animate.
218+
- Use `animateLayout` only on the element whose position or size changes.
219+
- Prefer one clear transition intent per mixin: entrance starts from a style,
220+
exit ends at a style.
221+
- Default to `...spring()` for duration and easing in most cases.
222+
- Keep DOM work in `handle.queueTask(...)` or `ref(...)`, not in render.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Assets and Browser Modules
2+
3+
## What This Covers
4+
5+
How to serve browser scripts and styles from source. Read this when the task
6+
involves:
7+
8+
- Configuring `createAssetServer` (`fileMap`, `allow`, `deny`, fingerprinting,
9+
compiler options)
10+
- Choosing between `staticFiles()` for already-built files and
11+
`createAssetServer()` for source assets that need import rewriting, preloads,
12+
or fingerprinted URLs
13+
- Generating script URLs or `<link rel="modulepreload">` tags for a client entry
14+
- Keeping server-only files out of the browser via `deny` rules
15+
16+
For routing the URL namespace itself, see `routing-and-controllers.md`. For
17+
client entry hydration, see `hydration-frames-navigation.md`.
18+
19+
## When To Reach For It
20+
21+
Use `remix/assets` when the app serves browser JavaScript, TypeScript, or CSS
22+
from source files. This is the right tool for client entrypoints, browser-only
23+
helpers, styles under `app/assets/`, and monorepo code that should be compiled
24+
and served under a public URL namespace.
25+
26+
Use `staticFiles()` for files that already exist on disk exactly as they should
27+
be served. Use `createAssetServer()` for source scripts or styles that need
28+
rewriting, dependency scanning, preloads, sourcemaps, or fingerprinted URLs.
29+
30+
## Default Pattern
31+
32+
```typescript
33+
import * as path from 'node:path'
34+
35+
import { createAssetServer } from 'remix/assets'
36+
import { createRouter } from 'remix/fetch-router'
37+
38+
let assetServer = createAssetServer({
39+
rootDir: path.resolve(import.meta.dirname, '..'),
40+
fileMap: {
41+
'/assets/app/*path': 'app/*path',
42+
'/assets/packages/*path': '../packages/*path',
43+
},
44+
allow: ['app/assets/**', '../packages/**'],
45+
deny: ['app/**/*.server.*'],
46+
target: { es: '2020', chrome: '109', safari: '16.4' },
47+
sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined,
48+
minify: process.env.NODE_ENV === 'production',
49+
scripts: {
50+
define: {
51+
'process.env.NODE_ENV': JSON.stringify(
52+
process.env.NODE_ENV ?? 'development',
53+
),
54+
},
55+
},
56+
})
57+
58+
let router = createRouter()
59+
60+
router.get('/assets/*path', ({ request }) => {
61+
return assetServer.fetch(request)
62+
})
63+
```
64+
65+
## Rules
66+
67+
- Treat `allow` and `deny` as the security boundary for browser-reachable source
68+
files.
69+
- Add a `deny` list for server-only modules such as `*.server.*`, private
70+
config, or other files that should never be exposed.
71+
- Set `rootDir` explicitly in monorepos so relative paths resolve from the
72+
intended project root.
73+
- `fileMap` keys are public URL patterns and values are root-relative file path
74+
patterns. They use `route-pattern` syntax on both sides.
75+
- Keep the same wildcard params on both sides of a `fileMap` entry so import
76+
rewriting can map source files back to public URLs.
77+
- CSS files are compiled and served alongside scripts. Local CSS `@import` rules
78+
are rewritten and fingerprinted with the same asset server routing rules.
79+
80+
## Rendering HTML
81+
82+
Use `getHref()` when you need the public URL for one module, and `getPreloads()`
83+
when you want `<link rel="modulepreload">` tags or `Link` headers for one or
84+
more entrypoints and their dependencies.
85+
86+
```typescript
87+
let entryHref = await assetServer.getHref('app/assets/entry.ts')
88+
let preloads = await assetServer.getPreloads(['app/assets/entry.ts'])
89+
```
90+
91+
Use this when rendering documents or layouts that boot browser behavior with a
92+
known client entry.
93+
94+
When resolving hydrated client entries during server rendering, pass the source
95+
entry ID from `clientEntry(import.meta.url, ...)` to `getHref()` inside
96+
`resolveClientEntry`. Keep export-name resolution in that render helper, and
97+
avoid hard-coding public asset URLs in source-owned component modules.
98+
99+
## Development vs Deployment
100+
101+
In development:
102+
103+
- Keep `watch` enabled so source changes are picked up without restarting the
104+
server
105+
- Prefer stable URLs with normal revalidation
106+
- Enable source maps when debugging browser code
107+
108+
In deployment:
109+
110+
- Set `watch: false`
111+
- Use `fingerprint: { buildId }` for long-lived immutable caching
112+
- Make sure `buildId` changes for each deploy
113+
114+
Fingerprinting assumes files on disk are stable and requires `watch: false`.
115+
116+
## Useful Compiler Options
117+
118+
- `minify` for production minification of scripts and styles
119+
- `sourceMaps` for `'external'` or `'inline'` source maps for scripts and styles
120+
- `sourceMapSourcePaths` for `'url'` or `'absolute'` source map paths
121+
- `target` as an object for shared browser targets and script-only ECMAScript
122+
output, such as `{ es: '2020', chrome: '109', safari: '16.4' }`
123+
- `scripts.define` to replace globals such as `process.env.NODE_ENV`
124+
- `scripts.external` to leave specific script imports untouched
125+
126+
Do not nest shared compiler options under `scripts`. Use top-level `minify`,
127+
`sourceMaps`, `sourceMapSourcePaths`, and `target` so they apply to styles as
128+
well as scripts.
129+
130+
## Lifecycle
131+
132+
If the asset server is long-lived and watching the file system, call
133+
`await assetServer.close()` when shutting down dev servers or disposing tests.

0 commit comments

Comments
 (0)