create-dstack 0.1.1 → 0.1.2

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.
@@ -1,138 +0,0 @@
1
- ---
2
- name: create-pr
3
- description: Turn current workspace changes into a published GitHub pull request by creating or reusing a branch, committing the changes, pushing the branch, and opening a PR with gh.
4
- ---
5
-
6
- ## 1. Trigger this skill
7
-
8
- - Use when the user asks to run end-to-end PR publishing for current local changes.
9
- - Use when the user asks for branch creation + commit + push + PR in one command.
10
-
11
- ## 2. Required tooling
12
-
13
- - `git` and `gh` are available and authenticated.
14
- - `scripts/create-pr.sh` exists at repo root.
15
- - Git remote `origin` is configured.
16
-
17
- ## 3. Inputs you can pass
18
-
19
- - `TARGET_BRANCH`: desired branch name.
20
- - `COMMIT_MESSAGE`: commit message used for the commit.
21
- - `PR_TITLE`: PR title shown in GitHub.
22
- - `PR_BODY_FILE`: optional markdown file path for PR body.
23
- - `BASE_BRANCH`: PR base branch (default `main`).
24
- - `GIT_REMOTE`: git remote name (default `origin`).
25
-
26
- ## 4. Process
27
-
28
- Run the workflow via script:
29
-
30
- ```bash
31
- TARGET_BRANCH=<branch-name> \
32
- COMMIT_MESSAGE="<message>" \
33
- PR_TITLE="<PR title>" \
34
- BASE_BRANCH=<base branch> \
35
- scripts/create-pr.sh
36
- ```
37
-
38
- Workflow order:
39
-
40
- - Create or checkout `TARGET_BRANCH`.
41
- - Add all local changes and create one commit with `COMMIT_MESSAGE`.
42
- - Push branch with upstream tracking to `GIT_REMOTE`.
43
- - Open a PR from `TARGET_BRANCH` to `BASE_BRANCH`.
44
- - Print PR URL on success.
45
-
46
- ## 5. Safety and failure behavior
47
-
48
- - Abort with clear error when no changes are present.
49
- - Abort when required commands are unavailable.
50
- - If PR creation fails after push, return a message with the pushed remote branch and stop cleanly.
51
-
52
- ## 6. PR description guidelines
53
-
54
- # Pull Request Guide
55
-
56
- This document describes how we write PRs for this project. The goal is for a reviewer — or anyone reading the PR months later — to understand what problem existed, what decision was made, and why, without having to read the code.
57
-
58
- ---
59
-
60
- ## Structure
61
-
62
- Every non-trivial PR should follow this structure:
63
-
64
- ### 1. Summary
65
-
66
- One short paragraph stating what this PR does and why it matters. No bullet lists here — write in prose.
67
-
68
- ### 2. Before vs After
69
-
70
- For architectural or flow changes, show the old and new as ASCII diagrams or code blocks. Label the problems with the old approach explicitly. The diagram should make the improvement self-evident.
71
-
72
- ```
73
- # Example
74
- Before:
75
- raw snapshot (~50K tokens)
76
- └─ agent A → agent B → agent C (sequential)
77
-
78
- After:
79
- formatted input (~1.5K tokens)
80
- ├─ agent A
81
- ├─ agent B (parallel)
82
- └─ agent C
83
- ```
84
-
85
- ### 3. Key Improvements
86
-
87
- Bullet list of the specific wins. Be concrete — use numbers.
88
-
89
- - "~97% token reduction" not "significant token reduction"
90
- - "MAE dropped from 3.5 to 2.1" not "improved accuracy"
91
- - "removed one sequential LLM hop" not "made it faster"
92
-
93
- ### 4. Deep-dive Sections
94
-
95
- For anything non-obvious — evaluation methodology, format design, data shape changes, model selection — give it its own titled section. Reviewers who want detail can read it; others can skip.
96
-
97
- ### 5. New Data Shape
98
-
99
- Whenever the output schema of a service or database record changes, show the new shape as a TypeScript snippet or JSON example.
100
-
101
- ### 6. Open TODOs
102
-
103
- Explicitly list what is **not** in this PR but should be done. Bold the item name, one sentence of context. This prevents follow-up questions and captures intent before it gets lost.
104
-
105
- - **Item name** — why it matters and what needs to happen
106
-
107
- ### 7. Test Plan
108
-
109
- Checkboxes with specific commands and specific things to verify. Not "test it works" but:
110
-
111
- - [ ] Run `doppler run -- npx tsx scripts/foo.ts` — verify output X
112
- - [ ] Process a new profile end-to-end — confirm data shape matches Y
113
- - [ ] Retry a partially-failed job — confirm only failed steps re-run
114
-
115
- ---
116
-
117
- ## Tone and Style
118
-
119
- - No emojis
120
- - No tool attribution footers (e.g. "Generated with X")
121
- - Write in plain declarative sentences: "Personality runs in parallel" not "We made personality run in parallel"
122
- - Use exact numbers where available
123
- - For model or approach comparisons, use a table
124
- - Don't hedge: say what happened
125
-
126
- ## Title Format
127
-
128
- Use [Conventional Commits](https://www.conventionalcommits.org/). Use title case for PR titles (capitalize principal words; lowercase short conjunctions like `and`, `or`, `for` unless they start the title). Preserve identifiers as they appear in code (`Cluster.AgentId`, API names, etc.).
129
-
130
- Keep titles under 72 characters. Use the description/body for details, not the title.
131
-
132
- ---
133
-
134
- ## PR Size
135
-
136
- - Prefer one focused PR over several small ones for tightly coupled changes
137
- - If a PR touches both a refactor and a new feature, split them unless they are inseparable
138
- - Infrastructure changes (schema, DB migrations) should be their own PR
@@ -1,45 +0,0 @@
1
- ---
2
- name: frontend-design
3
- description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
4
- license: Complete terms in LICENSE.txt
5
- ---
6
-
7
- This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
8
-
9
- The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
10
-
11
- ## Design Thinking
12
-
13
- Before coding, understand the context and commit to a BOLD aesthetic direction:
14
-
15
- - **Purpose**: What problem does this interface solve? Who uses it?
16
- - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
17
- - **Constraints**: Technical requirements (framework, performance, accessibility).
18
- - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
19
-
20
- **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
21
-
22
- Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
23
-
24
- - Production-grade and functional
25
- - Visually striking and memorable
26
- - Cohesive with a clear aesthetic point-of-view
27
- - Meticulously refined in every detail
28
-
29
- ## Frontend Aesthetics Guidelines
30
-
31
- Focus on:
32
-
33
- - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
34
- - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
35
- - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
36
- - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
37
- - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
38
-
39
- NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
40
-
41
- Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
42
-
43
- **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
44
-
45
- Remember: You is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
@@ -1,122 +0,0 @@
1
- ---
2
- name: make-interfaces-feel-better
3
- description: Design engineering principles for making interfaces feel polished. Use when building UI components, reviewing frontend code, implementing animations, hover states, shadows, borders, typography, micro-interactions, enter/exit animations, or any visual detail work. Triggers on UI polish, design details, "make it feel better", "feels off", stagger animations, border radius, optical alignment, font smoothing, tabular numbers, image outlines, box shadows.
4
- ---
5
-
6
- # Details that make interfaces feel better
7
-
8
- Great interfaces rarely come from a single thing. It's usually a collection of small details that compound into a great experience. Apply these principles when building or reviewing UI code.
9
-
10
- ## Quick Reference
11
-
12
- | Category | When to Use |
13
- | ----------------------------- | --------------------------------------------------------------------------------- |
14
- | [Typography](typography.md) | Text wrapping, font smoothing, tabular numbers |
15
- | [Surfaces](surfaces.md) | Border radius, optical alignment, shadows, image outlines, hit areas |
16
- | [Animations](animations.md) | Interruptible animations, enter/exit transitions, icon animations, scale on press |
17
- | [Performance](performance.md) | Transition specificity, `will-change` usage |
18
-
19
- ## Core Principles
20
-
21
- ### 1. Concentric Border Radius
22
-
23
- Outer radius = inner radius + padding. Mismatched radii on nested elements is the most common thing that makes interfaces feel off.
24
-
25
- ### 2. Optical Over Geometric Alignment
26
-
27
- When geometric centering looks off, align optically. Buttons with icons, play triangles, and asymmetric icons all need manual adjustment.
28
-
29
- ### 3. Shadows Over Borders
30
-
31
- Layer multiple transparent `box-shadow` values for natural depth. Shadows adapt to any background; solid borders don't.
32
-
33
- ### 4. Interruptible Animations
34
-
35
- Use CSS transitions for interactive state changes — they can be interrupted mid-animation. Reserve keyframes for staged sequences that run once.
36
-
37
- ### 5. Split and Stagger Enter Animations
38
-
39
- Don't animate a single container. Break content into semantic chunks and stagger each with ~100ms delay.
40
-
41
- ### 6. Subtle Exit Animations
42
-
43
- Use a small fixed `translateY` instead of full height. Exits should be softer than enters.
44
-
45
- ### 7. Contextual Icon Animations
46
-
47
- Animate icons with `opacity`, `scale`, and `blur` instead of toggling visibility. Use exactly these values: scale from `0.25` to `1`, opacity from `0` to `1`, blur from `4px` to `0px`. If the project has `motion` or `framer-motion` in `package.json`, use `transition: { type: "spring", duration: 0.3, bounce: 0 }` — bounce must always be `0`. If no motion library is installed, keep both icons in the DOM (one absolute-positioned) and cross-fade with CSS transitions using `cubic-bezier(0.2, 0, 0, 1)` — this gives both enter and exit animations without any dependency.
48
-
49
- ### 8. Font Smoothing
50
-
51
- Apply `-webkit-font-smoothing: antialiased` to the root layout on macOS for crisper text.
52
-
53
- ### 9. Tabular Numbers
54
-
55
- Use `font-variant-numeric: tabular-nums` for any dynamically updating numbers to prevent layout shift.
56
-
57
- ### 10. Text Wrapping
58
-
59
- Use `text-wrap: balance` on headings. Use `text-wrap: pretty` for body text to avoid orphans.
60
-
61
- ### 11. Image Outlines
62
-
63
- Add a subtle `1px` outline with low opacity to images for consistent depth.
64
-
65
- ### 12. Scale on Press
66
-
67
- A subtle `scale(0.96)` on click gives buttons tactile feedback. Always use `0.96`. Never use a value smaller than `0.95` — anything below feels exaggerated. Add a `static` prop to disable it when motion would be distracting.
68
-
69
- ### 13. Skip Animation on Page Load
70
-
71
- Use `initial={false}` on `AnimatePresence` to prevent enter animations on first render. Verify it doesn't break intentional entrance animations.
72
-
73
- ### 14. Never Use `transition: all`
74
-
75
- Always specify exact properties: `transition-property: scale, opacity`. Tailwind's `transition-transform` covers `transform, translate, scale, rotate`.
76
-
77
- ### 15. Use `will-change` Sparingly
78
-
79
- Only for `transform`, `opacity`, `filter` — properties the GPU can composite. Never use `will-change: all`. Only add when you notice first-frame stutter.
80
-
81
- ### 16. Minimum Hit Area
82
-
83
- Interactive elements need at least 40×40px hit area. Extend with a pseudo-element if the visible element is smaller. Never let hit areas of two elements overlap.
84
-
85
- ## Common Mistakes
86
-
87
- | Mistake | Fix |
88
- | -------------------------------------- | ------------------------------------------------- |
89
- | Same border radius on parent and child | Calculate `outerRadius = innerRadius + padding` |
90
- | Icons look off-center | Adjust optically with padding or fix SVG directly |
91
- | Hard borders between sections | Use layered `box-shadow` with transparency |
92
- | Jarring enter/exit animations | Split, stagger, and keep exits subtle |
93
- | Numbers cause layout shift | Apply `tabular-nums` |
94
- | Heavy text on macOS | Apply `antialiased` to root |
95
- | Animation plays on page load | Add `initial={false}` to `AnimatePresence` |
96
- | `transition: all` on elements | Specify exact properties |
97
- | First-frame animation stutter | Add `will-change: transform` (sparingly) |
98
- | Tiny hit areas on small controls | Extend with pseudo-element to 40×40px |
99
-
100
- ## Review Checklist
101
-
102
- - [ ] Nested rounded elements use concentric border radius
103
- - [ ] Icons are optically centered, not just geometrically
104
- - [ ] Shadows used instead of borders where appropriate
105
- - [ ] Enter animations are split and staggered
106
- - [ ] Exit animations are subtle
107
- - [ ] Dynamic numbers use tabular-nums
108
- - [ ] Font smoothing is applied
109
- - [ ] Headings use text-wrap: balance
110
- - [ ] Images have subtle outlines
111
- - [ ] Buttons use scale on press where appropriate
112
- - [ ] AnimatePresence uses `initial={false}` for default-state elements
113
- - [ ] No `transition: all` — only specific properties
114
- - [ ] `will-change` only on transform/opacity/filter, never `all`
115
- - [ ] Interactive elements have at least 40×40px hit area
116
-
117
- ## Reference Files
118
-
119
- - [typography.md](typography.md) — Text wrapping, font smoothing, tabular numbers
120
- - [surfaces.md](surfaces.md) — Border radius, optical alignment, shadows, image outlines
121
- - [animations.md](animations.md) — Interruptible animations, enter/exit transitions, icon animations, scale on press
122
- - [performance.md](performance.md) — Transition specificity, `will-change` usage
@@ -1,381 +0,0 @@
1
- # Animations
2
-
3
- Interruptible animations, enter/exit transitions, and contextual icon animations.
4
-
5
- ## Interruptible Animations
6
-
7
- Users change intent mid-interaction. If animations aren't interruptible, the interface feels broken.
8
-
9
- ### CSS Transitions vs. Keyframes
10
-
11
- | | CSS Transitions | CSS Keyframe Animations |
12
- | ----------------- | ----------------------------------------------------- | ---------------------------------------------------------- |
13
- | **Behavior** | Interpolate toward latest state | Run on a fixed timeline |
14
- | **Interruptible** | Yes — retargets mid-animation | No — restarts from beginning |
15
- | **Use for** | Interactive state changes (hover, toggle, open/close) | Staged sequences that run once (enter animations, loading) |
16
- | **Duration** | Adapts to remaining distance | Fixed regardless of state |
17
-
18
- ```css
19
- /* Good — interruptible transition for a toggle */
20
- .drawer {
21
- transform: translateX(-100%);
22
- transition: transform 200ms ease-out;
23
- }
24
- .drawer.open {
25
- transform: translateX(0);
26
- }
27
-
28
- /* Clicking again mid-animation smoothly reverses — no jank */
29
- ```
30
-
31
- ```css
32
- /* Bad — keyframe animation for interactive element */
33
- .drawer.open {
34
- animation: slideIn 200ms ease-out forwards;
35
- }
36
-
37
- /* Closing mid-animation snaps or restarts — feels broken */
38
- ```
39
-
40
- **Rule:** Always prefer CSS transitions for interactive elements. Reserve keyframes for one-shot sequences.
41
-
42
- ## Enter Animations: Split and Stagger
43
-
44
- Don't animate a single large container. Break content into semantic chunks and animate each individually.
45
-
46
- ### Step by Step
47
-
48
- 1. **Split** into logical groups (title, description, buttons)
49
- 2. **Stagger** with ~100ms delay between groups
50
- 3. **For titles**, consider splitting into individual words with ~80ms stagger
51
- 4. **Combine** `opacity`, `blur`, and `translateY` for the enter effect
52
-
53
- ### Code Example
54
-
55
- ```tsx
56
- // Motion (Framer Motion) — staggered enter
57
- function PageHeader() {
58
- return (
59
- <motion.div
60
- initial="hidden"
61
- animate="visible"
62
- variants={{
63
- visible: { transition: { staggerChildren: 0.1 } }
64
- }}
65
- >
66
- <motion.h1
67
- variants={{
68
- hidden: { opacity: 0, y: 12, filter: "blur(4px)" },
69
- visible: { opacity: 1, y: 0, filter: "blur(0px)" }
70
- }}
71
- >
72
- Welcome
73
- </motion.h1>
74
-
75
- <motion.p
76
- variants={{
77
- hidden: { opacity: 0, y: 12, filter: "blur(4px)" },
78
- visible: { opacity: 1, y: 0, filter: "blur(0px)" }
79
- }}
80
- >
81
- A description of the page.
82
- </motion.p>
83
-
84
- <motion.div
85
- variants={{
86
- hidden: { opacity: 0, y: 12, filter: "blur(4px)" },
87
- visible: { opacity: 1, y: 0, filter: "blur(0px)" }
88
- }}
89
- >
90
- <Button>Get started</Button>
91
- </motion.div>
92
- </motion.div>
93
- );
94
- }
95
- ```
96
-
97
- ### CSS-Only Stagger
98
-
99
- ```css
100
- .stagger-item {
101
- opacity: 0;
102
- transform: translateY(12px);
103
- filter: blur(4px);
104
- animation: fadeInUp 400ms ease-out forwards;
105
- }
106
-
107
- .stagger-item:nth-child(1) {
108
- animation-delay: 0ms;
109
- }
110
- .stagger-item:nth-child(2) {
111
- animation-delay: 100ms;
112
- }
113
- .stagger-item:nth-child(3) {
114
- animation-delay: 200ms;
115
- }
116
-
117
- @keyframes fadeInUp {
118
- to {
119
- opacity: 1;
120
- transform: translateY(0);
121
- filter: blur(0);
122
- }
123
- }
124
- ```
125
-
126
- ## Exit Animations
127
-
128
- Exit animations should be softer and less attention-grabbing than enter animations. The user's focus is moving to the next thing — don't fight for attention.
129
-
130
- ### Subtle Exit (Recommended)
131
-
132
- ```tsx
133
- // Small fixed translateY — indicates direction without drama
134
- <motion.div
135
- exit={{
136
- opacity: 0,
137
- y: -12,
138
- filter: "blur(4px)",
139
- transition: { duration: 0.15, ease: "easeIn" }
140
- }}
141
- >
142
- {content}
143
- </motion.div>
144
- ```
145
-
146
- ### Full Exit (When Context Matters)
147
-
148
- ```tsx
149
- // Slide fully out — use when spatial context is important
150
- // (e.g., a card returning to a list, a drawer closing)
151
- <motion.div
152
- exit={{
153
- opacity: 0,
154
- x: "-100%",
155
- transition: { duration: 0.2, ease: "easeIn" }
156
- }}
157
- >
158
- {content}
159
- </motion.div>
160
- ```
161
-
162
- ### Good vs. Bad
163
-
164
- ```css
165
- /* Good — subtle exit */
166
- .item-exit {
167
- opacity: 0;
168
- transform: translateY(-12px);
169
- transition:
170
- opacity 150ms ease-in,
171
- transform 150ms ease-in;
172
- }
173
-
174
- /* Bad — dramatic exit that steals focus */
175
- .item-exit {
176
- opacity: 0;
177
- transform: translateY(-100%) scale(0.5);
178
- transition: all 400ms ease-in;
179
- }
180
-
181
- /* Bad — no exit animation at all (element just vanishes) */
182
- .item-exit {
183
- display: none;
184
- }
185
- ```
186
-
187
- **Key points:**
188
-
189
- - Use a small fixed `translateY` (e.g., `-12px`) instead of the full container height
190
- - Keep some directional movement to indicate where the element went
191
- - Exit duration should be shorter than enter duration (150ms vs 300ms)
192
- - Don't remove exit animations entirely — subtle motion preserves context
193
-
194
- ## Contextual Icon Animations
195
-
196
- When icons appear or disappear contextually (on hover, on state change), animate them with `opacity`, `scale`, and `blur` rather than just toggling visibility.
197
-
198
- ### Motion Example
199
-
200
- ```tsx
201
- import { AnimatePresence, motion } from "motion/react";
202
-
203
- function IconButton({ isActive, icon: Icon }) {
204
- return (
205
- <button>
206
- <AnimatePresence mode="popLayout">
207
- <motion.span
208
- key={isActive ? "active" : "inactive"}
209
- initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
210
- animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
211
- exit={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
212
- transition={{ type: "spring", duration: 0.3, bounce: 0 }}
213
- >
214
- <Icon />
215
- </motion.span>
216
- </AnimatePresence>
217
- </button>
218
- );
219
- }
220
- ```
221
-
222
- ### CSS Transition Approach (No Motion)
223
-
224
- If the project doesn't use Motion (Framer Motion), keep both icons in the DOM and cross-fade them with CSS transitions. Because neither icon unmounts, both enter and exit animate smoothly.
225
-
226
- The trick: one icon is absolutely positioned on top of the other. Toggling state cross-fades them — the entering icon scales up from `0.25` while the exiting icon scales down to `0.25`, both with opacity and blur.
227
-
228
- ```tsx
229
- function IconButton({ isActive, ActiveIcon, InactiveIcon }) {
230
- return (
231
- <button>
232
- <div className="relative">
233
- <div
234
- className={cn(
235
- "absolute inset-0 flex items-center justify-center",
236
- "transition-[opacity,filter,scale] duration-300",
237
- "cubic-bezier(0.2, 0, 0, 1)",
238
- isActive ? "scale-100 opacity-100 blur-0" : "scale-[0.25] opacity-0 blur-xs"
239
- )}
240
- >
241
- <ActiveIcon />
242
- </div>
243
- <div
244
- className={cn(
245
- "transition-[opacity,filter,scale] duration-300",
246
- "cubic-bezier(0.2, 0, 0, 1)",
247
- isActive ? "scale-[0.25] opacity-0 blur-xs" : "scale-100 opacity-100 blur-0"
248
- )}
249
- >
250
- <InactiveIcon />
251
- </div>
252
- </div>
253
- </button>
254
- );
255
- }
256
- ```
257
-
258
- The non-absolute icon (InactiveIcon) defines the layout size. The absolute icon (ActiveIcon) overlays it without affecting flow.
259
-
260
- ### Choosing Between Motion and CSS
261
-
262
- | | Motion (Framer Motion) | CSS transitions (both icons in DOM) |
263
- | ------------------- | ----------------------------------- | ------------------------------------------------------ |
264
- | **Enter animation** | Yes | Yes |
265
- | **Exit animation** | Yes (via `AnimatePresence`) | Yes (cross-fade — icon never unmounts) |
266
- | **Spring physics** | Yes | No — use `cubic-bezier(0.2, 0, 0, 1)` as approximation |
267
- | **When to use** | Project already uses `motion/react` | No motion dependency, or keeping bundle small |
268
-
269
- **Rule:** Check the project's `package.json` for `motion` or `framer-motion`. If present, use the Motion approach. If not, use the CSS cross-fade pattern — don't add a dependency just for icon transitions.
270
-
271
- ### When to Animate Icons
272
-
273
- | Animate | Don't animate |
274
- | ----------------------------------------------- | ------------------------------- |
275
- | Icons that appear on hover (action buttons) | Static navigation icons |
276
- | State change icons (play → pause, like → liked) | Decorative icons |
277
- | Icons in contextual toolbars | Icons that are always visible |
278
- | Loading/success state indicators | Icon labels (text next to icon) |
279
-
280
- **Important:** Always use exactly these values for contextual icon animations — do not deviate:
281
-
282
- - `scale`: `0.25` → `1` (never use `0.5` or `0.6`)
283
- - `opacity`: `0` → `1`
284
- - `filter`: `"blur(4px)"` → `"blur(0px)"`
285
- - `transition`: `{ type: "spring", duration: 0.3, bounce: 0 }` — **bounce must always be `0`**, never `0.1` or any other value
286
-
287
- ## Scale on Press
288
-
289
- A subtle scale-down on click gives buttons tactile feedback. Always use `scale(0.96)`. Never use a value smaller than `0.95` — anything below feels exaggerated. Use CSS transitions for interruptibility — if the user releases mid-press, it should smoothly return.
290
-
291
- Not every button needs this. Add a `static` prop to your button component that disables the scale effect when the motion would be distracting.
292
-
293
- ### CSS Example
294
-
295
- ```css
296
- .button {
297
- transition-property: scale;
298
- transition-duration: 150ms;
299
- transition-timing-function: ease-out;
300
- }
301
-
302
- .button:active {
303
- scale: 0.96;
304
- }
305
- ```
306
-
307
- ### Tailwind Example
308
-
309
- ```tsx
310
- <button className="transition-transform duration-150 ease-out active:scale-[0.96]">Click me</button>
311
- ```
312
-
313
- ### Motion Example
314
-
315
- ```tsx
316
- <motion.button whileTap={{ scale: 0.96 }}>Click me</motion.button>
317
- ```
318
-
319
- ### Static Prop Pattern
320
-
321
- Extract the scale class into a variable and conditionally apply it based on a `static` prop:
322
-
323
- ```tsx
324
- const tapScale = "active:not-disabled:scale-[0.96]";
325
-
326
- function Button({ static: isStatic, className, children, ...props }) {
327
- return (
328
- <button
329
- className={cn(
330
- "transition-transform duration-150 ease-out",
331
- !isStatic && tapScale,
332
- className,
333
- )}
334
- {...props}
335
- >
336
- {children}
337
- </button>
338
- );
339
- }
340
-
341
- // Usage
342
- <Button>Click me</Button> {/* scales on press */}
343
- <Button static>Submit</Button> {/* no scale */}
344
- ```
345
-
346
- ## Skip Animation on Page Load
347
-
348
- Use `initial={false}` on `AnimatePresence` to prevent enter animations from firing on first render. Elements that are already in their default state shouldn't animate in on page load — only on subsequent state changes.
349
-
350
- ### When It Works
351
-
352
- ```tsx
353
- // Good — icon doesn't animate in on mount, only on state change
354
- <AnimatePresence initial={false} mode="popLayout">
355
- <motion.span
356
- key={isActive ? "active" : "inactive"}
357
- initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
358
- animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
359
- exit={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
360
- >
361
- <Icon />
362
- </motion.span>
363
- </AnimatePresence>
364
- ```
365
-
366
- Works well for: icon swaps, toggles, tabs, segmented controls — anything that has a default state on page load.
367
-
368
- ### When It Breaks
369
-
370
- Don't use `initial={false}` when the component relies on its `initial` prop to set up a first-time enter animation, like a staggered page hero or a loading state. In those cases, removing the initial animation skips the entire entrance.
371
-
372
- ```tsx
373
- // Bad — initial={false} would skip the staggered page enter entirely
374
- <AnimatePresence initial={false}>
375
- <motion.div initial="hidden" animate="visible" variants={...}>
376
- ...
377
- </motion.div>
378
- </AnimatePresence>
379
- ```
380
-
381
- Verify the component still looks right on a full page refresh before applying this.