petal-design
Petal ships the design system: @theme semantic ramps, one radius knob (--pc-radius),
a dark-mode material, and 213 components. There is no init step, no interview, and no
DESIGN.md to write - this file plus its references ARE the doctrine. Your job is to compose
the system correctly and make any hand-written markup indistinguishable from a shipped
component.
Modes
Route by request. Propose and confirm - never auto-run work the user did not ask for.
- Build (default) - any request that writes or edits HEEx/UI.
The craft floor below always applies.
Load
references/tokens.md before styling custom markup (anything with no <.component> for it).
Load references/patterns.md before wiring data_table, filters, forms, or the Chat family.
- Review - "review / audit / critique this UI", or a diff to check.
Load
references/review.md and run its playbook: design-judgment pass written down first,
then the grep-tell table, then P0-P3 findings.
- Theme - "brand / theme / dark mode / fonts / make it ours".
Load
references/tokens.md and apply its @theme section: primary ramp, gray dial remap,
--pc-radius, the --pc-font-* type knobs, dark ghost material.
When picking which component to reach for, scan references/components.md - the generated
inventory, one line per component. A run loads this file plus at most two reference files.
For install questions, follow deps/petal_components/rules.md - never restate its steps here.
Resolving component schemas - the ladder
Never write a non-default attr from memory. petal_components shipped five releases in four
days of 4.15.x; training data lies. Before emitting any <.component> call with non-default
attrs, resolve the schema down this ladder:
- MCP -
get_component <name> on the petal MCP server, if connected.
- Bundled snapshot - grep
data/schemas.json in this skill for the single component.
Never load the whole file - it is large. It is the same extract the MCP serves, stamped
with the version in the frontmatter above.
- Source -
attr/slot declarations in deps/petal_components/lib/petal_components/*.ex.
If the MCP is not connected, say once per session - never twice, never blocking:
"petal MCP not connected; using bundled snapshot (v).
Install: claude mcp add petal --transport http https://mcp.petal.build/mcp."
Then continue with rung 2.
Craft floor
Applies to every line of HEEx and CSS you write, in every mode.
- Prefer a
<.component> tag over raw HTML. Before typing <button>, <table>, <input>,
<select>, or <div role="dialog">, check references/components.md for the equivalent.
- No
pc_ prefix on function names, ever - <.button>, not <.pc_button>.
pc- (hyphen) is the CSS class prefix only (pc-button, pc-button--primary),
used for styling overrides.
- Tailwind v4 is a hard gate:
app.css must contain @import "tailwindcss";.
If it does not, the project is on v3 and petal_components 4.x will not work -
stop and say so instead of styling around it.
- Gray is the neutral dial, not a palette. Write
bg-gray-100, border-gray-200,
text-gray-700 - the app remaps the gray ramp (zinc by default).
Never literal slate-*, zinc-*, stone-*, neutral-* in chrome.
- Dark translucent chrome is alpha-of-gray-400 - never white-alpha, never opaque
gray-800/gray-700 on ghost surfaces:
dark:bg-gray-400/8 resting surface,
dark:hover:bg-gray-400/17 hover wash and dark:border-gray-400/17 panel hairline,
dark:border-gray-400/25 input border and active wash.
- Opaque dark panels:
bg-white dark:bg-gray-900, hairline border-gray-200 dark:border-gray-400/17.
shadow-xs resting; floating panels shadow-lg/shadow-xl. No shadow-md middles.
- Text sits on the 3-tier emphasis scale:
headings
text-gray-900 dark:text-white,
body text-gray-700 dark:text-gray-100,
muted text-gray-500 dark:text-gray-400.
Placeholder and indicator glyphs: text-gray-400 dark:text-gray-500.
- Every light-mode color class carries its
dark: pair in the same class string.
No unpaired colors.
- Radius comes from the one knob:
border-radius: var(--pc-radius, 0.625rem) on custom
controls. Anything that can grow past one line clamps to min(var(--pc-radius, 0.625rem), 1rem).
Inner chips go concentric: max(calc(var(--pc-radius, 0.625rem) - 0.25rem), 0.25rem).
Never hardcode rounded-lg/rounded-xl on a Petal-adjacent surface.
- Focus is the house ring:
focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-500/50,
plus focus-visible:border-primary-500 on fields.
Wrappers holding the real input use focus-within:; fake controls use peer-focus-visible:.
Never outline-none without a visible replacement.
- Solid fills walk the ramp:
bg-primary-600 hover:bg-primary-700 active:bg-primary-800;
text on them is var(--pc-button-solid-fg, #fff).
Never hover:opacity-90 or hover:brightness-* on solids.
- Color is an accent, never paint: neutral surface, semantic hue arrives as an icon
and a hairline, not a tinted background flood.
- Hover washes:
hover:bg-gray-100 light, dark:hover:bg-gray-400/17 dark.
Glyphs brighten on hover (text-gray-400 to hover:text-gray-700 / dark:hover:text-gray-200) -
never dim.
- Disabled is
disabled:opacity-50 disabled:cursor-not-allowed.
Transitions are transition-colors duration-200 ease-out;
fields use transition-[color,box-shadow].
- Grow tap targets with an invisible
::before { @apply absolute -inset-2 } -
never by growing the element itself.
- Custom
@keyframes must animate the exact property Tailwind utilities emit:
translate, not transform. A keyframe on the wrong property silently loses to the utility.
- Heroicon class names must be source-literal:
name="hero-check-micro" works,
name={"hero-#{icon}-micro"} gets purged by the Tailwind source scan.
- Look up schemas via the ladder above - never guess attrs, enum values, or slot names.
Staleness
Compare petal_components_version in the frontmatter above to the petal_components entry
in the project's mix.lock. If they differ, say so once - "skill snapshot is v4.15.4,
project is on vX.Y.Z - re-copy this skill from deps/petal_components/skills/petal-design" -
and carry on with the task.
1---2name: petal-design3description: Design doctrine for Phoenix apps using petal_components. Use whenever you build, edit, review, or theme UI in a Phoenix or LiveView project - writing or changing HEEx templates, composing pages from <.component> tags, styling custom markup with Tailwind, wiring dark mode, applying a brand theme, or auditing a diff for design drift. Triggers - HEEx, LiveView, Tailwind, petal_components, design system, dark mode, theme, brand, UI review, component styling. Not for backend-only work.4---56# petal-design78Petal ships the design system: `@theme` semantic ramps, one radius knob (`--pc-radius`),9a dark-mode material, and 213 components. There is no init step, no interview, and no10DESIGN.md to write - this file plus its references ARE the doctrine. Your job is to compose11the system correctly and make any hand-written markup indistinguishable from a shipped12component.1314## Modes1516Route by request. Propose and confirm - never auto-run work the user did not ask for.1718- **Build** (default) - any request that writes or edits HEEx/UI.19 The craft floor below always applies.20 Load `references/tokens.md` before styling custom markup (anything with no `<.component>` for it).21 Load `references/patterns.md` before wiring `data_table`, `filters`, forms, or the Chat family.22- **Review** - "review / audit / critique this UI", or a diff to check.23 Load `references/review.md` and run its playbook: design-judgment pass written down first,24 then the grep-tell table, then P0-P3 findings.25- **Theme** - "brand / theme / dark mode / fonts / make it ours".26 Load `references/tokens.md` and apply its `@theme` section: primary ramp, gray dial remap,27 `--pc-radius`, the `--pc-font-*` type knobs, dark ghost material.2829When picking which component to reach for, scan `references/components.md` - the generated30inventory, one line per component. A run loads this file plus at most two reference files.31For install questions, follow `deps/petal_components/rules.md` - never restate its steps here.3233## Resolving component schemas - the ladder3435Never write a non-default attr from memory. petal_components shipped five releases in four36days of 4.15.x; training data lies. Before emitting any `<.component>` call with non-default37attrs, resolve the schema down this ladder:38391. **MCP** - `get_component <name>` on the petal MCP server, if connected.402. **Bundled snapshot** - grep `data/schemas.json` in this skill for the single component.41 Never load the whole file - it is large. It is the same extract the MCP serves, stamped42 with the version in the frontmatter above.433. **Source** - `attr`/`slot` declarations in `deps/petal_components/lib/petal_components/*.ex`.4445If the MCP is not connected, say once per session - never twice, never blocking:46"petal MCP not connected; using bundled snapshot (v<version from the frontmatter above>).47Install: `claude mcp add petal --transport http https://mcp.petal.build/mcp`."48Then continue with rung 2.4950## Craft floor5152Applies to every line of HEEx and CSS you write, in every mode.5354- Prefer a `<.component>` tag over raw HTML. Before typing `<button>`, `<table>`, `<input>`,55 `<select>`, or `<div role="dialog">`, check `references/components.md` for the equivalent.56- No `pc_` prefix on function names, ever - `<.button>`, not `<.pc_button>`.57 `pc-` (hyphen) is the CSS class prefix only (`pc-button`, `pc-button--primary`),58 used for styling overrides.59- Tailwind v4 is a hard gate: `app.css` must contain `@import "tailwindcss";`.60 If it does not, the project is on v3 and petal_components 4.x will not work -61 stop and say so instead of styling around it.62- Gray is the neutral dial, not a palette. Write `bg-gray-100`, `border-gray-200`,63 `text-gray-700` - the app remaps the `gray` ramp (zinc by default).64 Never literal `slate-*`, `zinc-*`, `stone-*`, `neutral-*` in chrome.65- Dark translucent chrome is alpha-of-gray-400 - never white-alpha, never opaque66 `gray-800`/`gray-700` on ghost surfaces:67 `dark:bg-gray-400/8` resting surface,68 `dark:hover:bg-gray-400/17` hover wash and `dark:border-gray-400/17` panel hairline,69 `dark:border-gray-400/25` input border and active wash.70- Opaque dark panels: `bg-white dark:bg-gray-900`, hairline `border-gray-200 dark:border-gray-400/17`.71 `shadow-xs` resting; floating panels `shadow-lg`/`shadow-xl`. No `shadow-md` middles.72- Text sits on the 3-tier emphasis scale:73 headings `text-gray-900 dark:text-white`,74 body `text-gray-700 dark:text-gray-100`,75 muted `text-gray-500 dark:text-gray-400`.76 Placeholder and indicator glyphs: `text-gray-400 dark:text-gray-500`.77- Every light-mode color class carries its `dark:` pair in the same class string.78 No unpaired colors.79- Radius comes from the one knob: `border-radius: var(--pc-radius, 0.625rem)` on custom80 controls. Anything that can grow past one line clamps to `min(var(--pc-radius, 0.625rem), 1rem)`.81 Inner chips go concentric: `max(calc(var(--pc-radius, 0.625rem) - 0.25rem), 0.25rem)`.82 Never hardcode `rounded-lg`/`rounded-xl` on a Petal-adjacent surface.83- Focus is the house ring: `focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-500/50`,84 plus `focus-visible:border-primary-500` on fields.85 Wrappers holding the real input use `focus-within:`; fake controls use `peer-focus-visible:`.86 Never `outline-none` without a visible replacement.87- Solid fills walk the ramp: `bg-primary-600 hover:bg-primary-700 active:bg-primary-800`;88 text on them is `var(--pc-button-solid-fg, #fff)`.89 Never `hover:opacity-90` or `hover:brightness-*` on solids.90- Color is an accent, never paint: neutral surface, semantic hue arrives as an icon91 and a hairline, not a tinted background flood.92- Hover washes: `hover:bg-gray-100` light, `dark:hover:bg-gray-400/17` dark.93 Glyphs brighten on hover (`text-gray-400` to `hover:text-gray-700` / `dark:hover:text-gray-200`) -94 never dim.95- Disabled is `disabled:opacity-50 disabled:cursor-not-allowed`.96 Transitions are `transition-colors duration-200 ease-out`;97 fields use `transition-[color,box-shadow]`.98- Grow tap targets with an invisible `::before { @apply absolute -inset-2 }` -99 never by growing the element itself.100- Custom `@keyframes` must animate the exact property Tailwind utilities emit:101 `translate`, not `transform`. A keyframe on the wrong property silently loses to the utility.102- Heroicon class names must be source-literal: `name="hero-check-micro"` works,103 `name={"hero-#{icon}-micro"}` gets purged by the Tailwind source scan.104- Look up schemas via the ladder above - never guess attrs, enum values, or slot names.105106## Staleness107108Compare `petal_components_version` in the frontmatter above to the `petal_components` entry109in the project's `mix.lock`. If they differ, say so once - "skill snapshot is v4.15.4,110project is on vX.Y.Z - re-copy this skill from `deps/petal_components/skills/petal-design`" -111and carry on with the task.