|
| 1 | +--- |
| 2 | +name: create-element |
| 3 | +description: > |
| 4 | + Create a new pf-v6 element from a PatternFly React component that has |
| 5 | + no existing v5 web component. Use when asked to "create", "build", |
| 6 | + "implement", or "add" a new element from scratch. |
| 7 | +tools: Read, Glob, Grep, Bash |
| 8 | +--- |
| 9 | + |
| 10 | +# Create New v6 Element |
| 11 | + |
| 12 | +Build a new `pf-v6-{name}` element from the PatternFly React v6 source. |
| 13 | +No existing v5 element to start from -- everything written fresh. |
| 14 | +Aim for end-user experience parity with the corresponding v6 react component. |
| 15 | + |
| 16 | +## Prerequisites |
| 17 | + |
| 18 | +- `cem serve` running (`npm run dev`) |
| 19 | +- Chrome MCP connected |
| 20 | +- React source in `../patternfly-react/packages/react-core/src/components/` |
| 21 | +- SCSS source in `../patternfly/src/patternfly/components/` (for reference) |
| 22 | + |
| 23 | +## Workflow |
| 24 | + |
| 25 | +### Phase 1: Analyze React Source |
| 26 | + |
| 27 | +1. Read the React component: |
| 28 | + ``` |
| 29 | + ../patternfly-react/packages/react-core/src/components/{Name}/ |
| 30 | + ``` |
| 31 | + See also the corresponding docs page e.g. |
| 32 | + https://patternfly.org/components/accordion/ |
| 33 | + |
| 34 | + See demos in |
| 35 | + ``` |
| 36 | + ../patternfly-react/packages/react-core/src/demos/ |
| 37 | + ``` |
| 38 | + |
| 39 | +2. Read the stylesheet for that element via the Chrome MCP, pointed at the element page |
| 40 | + ``` |
| 41 | + https://patternfly.org/components/{Name}/ |
| 42 | + ``` |
| 43 | + |
| 44 | +3. Read the React test files for behavioral expectations: |
| 45 | + ``` |
| 46 | + ../patternfly-react/packages/react-core/src/components/{Name}/__tests__/ |
| 47 | + ``` |
| 48 | + |
| 49 | +4. Identify: |
| 50 | + - Props that map to attributes vs properties vs slots vs CSS |
| 51 | + - Events (callbacks) that map to DOM events |
| 52 | + - Internal state management |
| 53 | + - Form association needs |
| 54 | + - Sub-components needed |
| 55 | + - Dependencies on other PF elements (must already exist as pf-v6-*) |
| 56 | + |
| 57 | +### Phase 2: Enumerate Demos |
| 58 | + |
| 59 | +Fetch the patternfly.org component page to identify all demos: |
| 60 | +``` |
| 61 | +https://www.patternfly.org/components/{name} |
| 62 | +``` |
| 63 | + |
| 64 | +Each demo has a full-screen URL: |
| 65 | +``` |
| 66 | +https://www.patternfly.org/components/{name}/react/{demo-slug}/ |
| 67 | +``` |
| 68 | + |
| 69 | +Identify which demo source file corresponds to each docs site demo |
| 70 | + |
| 71 | +Map React demos to our demo files: |
| 72 | +``` |
| 73 | +React demo name -> Our demo file |
| 74 | +"Variant examples" -> demo/variant-examples.html |
| 75 | +"Disabled buttons" -> demo/disabled-buttons.html |
| 76 | +``` |
| 77 | + |
| 78 | +Additional demos for WC-specific APIs (slots, CSS parts, FACE) use our |
| 79 | +own names alongside. |
| 80 | + |
| 81 | +Collect description text from patternfly.org for YAML frontmatter. |
| 82 | + |
| 83 | +### Phase 3: Design Web-Native API |
| 84 | + |
| 85 | +Read `.claude/ADVICE.md` first -- distilled rules from |
| 86 | +4800+ PR reviews. This is the canonical reference for all API decisions. |
| 87 | + |
| 88 | +Full freedom on API design -- no legacy to preserve. Design the best |
| 89 | +web component API for this element's purpose. |
| 90 | + |
| 91 | +#### React-to-WC Translation Rules |
| 92 | +- React `children` -> default slot |
| 93 | +- React render props -> named slots |
| 94 | +- React `className` -> not needed (shadow DOM) |
| 95 | +- React `onChange` callback -> `change` event (Event subclass) |
| 96 | +- React `isExpanded` prop -> `expanded` boolean attribute |
| 97 | +- React `variant="primary"` -> `variant="primary"` (often 1:1) |
| 98 | +- React component composition -> slot composition - be careful of SSR DOM |
| 99 | + access |
| 100 | +- React context -> CSS custom properties, or `@lit/context` if JS is required |
| 101 | +- React `useEffect` -> `willUpdate`/`updated` lifecycle |
| 102 | +- React hooks -> Lit reactive controllers |
| 103 | + |
| 104 | +#### Prefer Native Web Platform |
| 105 | +- FACE instead of wrapping native inputs where feasible |
| 106 | +- `InternalsController` for all ElementInternals (ARIA, form association). |
| 107 | + Never use raw `attachInternals()` -- always go through InternalsController |
| 108 | +- When aria-label is indicated (e.g. icon-button), provide an `accessible-label` |
| 109 | + attribute |
| 110 | +- Slots for composition, not render-prop equivalents |
| 111 | +- Native `<dialog>` instead of JS-managed modals |
| 112 | +- Container queries instead of responsive/breakpoint attributes |
| 113 | +- `<details>` for expandable content (free a11y) |
| 114 | + |
| 115 | +#### API Design Rules (apply to each surface) |
| 116 | + |
| 117 | +IMPORTANT: in all cases where a react api does not cleanly map to a web component |
| 118 | +API, surface that to the user. |
| 119 | + |
| 120 | +**Key principle:** easy to add, hard to remove. Ship less when in doubt. |
| 121 | + |
| 122 | +##### Templates |
| 123 | +- Prefer ID selectors in shadow DOM, |
| 124 | +- Avoid BEM, avoid using .pf-* classes internally in shadow root |
| 125 | +- Minimize wrappers; prefer `<slot>` directly |
| 126 | +- Vertical attribute formatting when >2 attrs |
| 127 | +- False case first in ternaries when used in templates |
| 128 | +- `slotchange` bubbles; listen on shadow root |
| 129 | + |
| 130 | +##### Attributes & Properties: |
| 131 | +- Do not require light DOM classes. Use attributes instead |
| 132 | +- Enum-style `variant` over multiple booleans (exclusive, not stackable) |
| 133 | +- Boolean attributes: present = true, absent = false. No `is-` prefix. |
| 134 | +- Don't reflect booleans to `"true"|"false"` strings |
| 135 | +- Don't default reflected boolean attributes to true, it will be impossible to unset in |
| 136 | + HTML |
| 137 | +- Avoid array/object properties, exceptions for convenience APIs like |
| 138 | + `pfselect.items = []` |
| 139 | +- Don't reflect array or object properties |
| 140 | +- Multi-word attributes: dash-case with camelCase class fields |
| 141 | +- Don't expose `aria-*` as public API; abstract behind custom attributes |
| 142 | +- Attribute/slot pairs for content that could be plain or rich |
| 143 | + e.g. |
| 144 | + ```html |
| 145 | + <pf-v6-foo description="plaintext"></pf-v6-foo> |
| 146 | + <pf-v6-foo><span slot="description"><em>Rich</em> text</pf-v6-foo> |
| 147 | + ``` |
| 148 | +- All attributes must have sensible defaults |
| 149 | + |
| 150 | +##### Slots: |
| 151 | +- Default slot for primary content |
| 152 | +- Semantic names, not positional "header", not "top" |
| 153 | +- Don't provide placeholder "lorem ipsum" content; but do provide default content when |
| 154 | + sensible |
| 155 | +- Prescriptive descriptions in docs ("Label text" not "The label slot") - see https://bennypowers.dev/cem/docs/usage/effective-mcp-descriptions/ |
| 156 | +- Hide empty slot containers, but be wary of SSR problems, use SlotController's |
| 157 | + ssr-hint-* attributes |
| 158 | +- WARNING: `:empty` does not work on slots, `:has-slotted` is not yet widely available |
| 159 | + |
| 160 | +##### Events: |
| 161 | +- Event subclasses, not `CustomEvent` with detail |
| 162 | +- Cancelable for destructive/state-changing actions, or when users may wish to prevent default. |
| 163 | +- Match native event names when wrapping native elements |
| 164 | + |
| 165 | +##### Element CSS |
| 166 | +- Use Native nesting |
| 167 | +- Use `light-dark()` |
| 168 | +- Use logical properties instead of directionally |
| 169 | +- You may and should use any CSS features which are Baseline 2024 or earlier, |
| 170 | + but no later |
| 171 | +- Don't nest `:host([attr])` with `&`, this doesn't work. |
| 172 | +- Don't use :host:has() or :host(:has()) - these are not quite ready |
| 173 | + cross-browser |
| 174 | +- never use :host-context, it does not exist |
| 175 | +- `pointer-events: none` on disabled hosts |
| 176 | + |
| 177 | +##### CSS Custom Properties: |
| 178 | +- Use `--_` prefix for private custom properties, for example, when a css custom |
| 179 | + property is defined purely to make the css file nicer, or for state management, |
| 180 | + but is not intended for public use. |
| 181 | +- Avoid setting public css properties on the :host selector, rather, reference |
| 182 | + them as fallback defaults at use sites |
| 183 | +- Don't duplicate native CSS capabilities |
| 184 | +- Fallback defaults at use site, not on `:host` |
| 185 | +- **MUST match patternfly-react** (`--pf-v6-c-{component}--*`) |
| 186 | + Only exception: shadow DOM structure vastly different from React's flat DOM. |
| 187 | + |
| 188 | + If you find a case where a given CSS custom property in the React source is tightly |
| 189 | + coupled to a given DOM structure, in a way which inhibits our ability to freely |
| 190 | + factor the best web component, raise that to the user. |
| 191 | + |
| 192 | +##### CSS Parts: |
| 193 | +- Name after internal element. Only expose with clear use cases. |
| 194 | +- Question aggressively -- hard to remove. |
| 195 | + |
| 196 | +### Phase 4: Implement |
| 197 | + |
| 198 | +1. Create element directory: `elements/pf-v6-{name}/` |
| 199 | +2. Write main element class: |
| 200 | + - `@customElement('pf-v6-{name}')` decorator |
| 201 | + - `static readonly styles` array |
| 202 | + - Reactive properties with `@property` |
| 203 | + - InternalsController for ARIA and form association (never raw attachInternals) |
| 204 | + - FACE patterns if form-associated |
| 205 | + - Template in `render()` method |
| 206 | + |
| 207 | +3. Write CSS: |
| 208 | + - Translate SCSS to native CSS (nesting, light-dark(), logical properties) |
| 209 | + - Shadow DOM scoping (IDs not BEM) |
| 210 | + - Token-derived fallback values from PF token source |
| 211 | + - box-sizing reset |
| 212 | + - **MUST match patternfly-react CSS custom properties** (see CLAUDE.md) |
| 213 | + |
| 214 | +4. Create sub-elements if needed (only for a11y/composition reasons) |
| 215 | +5. Update `elements/package.json` exports |
| 216 | + |
| 217 | +### Phase 5: Create Demos |
| 218 | + |
| 219 | +Create HTML partial demos in `demo/`. Demos are partials (no `<!DOCTYPE>`, |
| 220 | +`<html>`, `<head>`). `cem serve` wraps them. |
| 221 | + |
| 222 | +For each demo from Phase 2: |
| 223 | +1. Create `demo/{demo-slug}.html` matching patternfly.org name |
| 224 | +2. Add YAML frontmatter: |
| 225 | + ```html |
| 226 | + --- |
| 227 | + name: Variant examples |
| 228 | + description: Button variant examples showing primary, secondary, and link styles. |
| 229 | + --- |
| 230 | + ``` |
| 231 | +3. Inline `<script type="module">` with element import |
| 232 | +4. No wrapper divs; minimal markup |
| 233 | + |
| 234 | +`index.html` = simplest possible usage. |
| 235 | + |
| 236 | +Additional WC-specific demos: |
| 237 | +- `demo/slots.html` - slot composition patterns |
| 238 | +- `demo/form-associated.html` - FACE form integration (if applicable) |
| 239 | +- `demo/css-custom-properties.html` - theming overrides |
| 240 | + |
| 241 | +### Phase 6: Visual Comparison with Chrome MCP |
| 242 | + |
| 243 | +**CRITICAL**: `?rendering=chromeless` on ALL cem serve URLs. |
| 244 | + |
| 245 | +For each demo, compare against patternfly.org: |
| 246 | + |
| 247 | +1. Open our demo: |
| 248 | + ``` |
| 249 | + navigate_page -> http://localhost:8000/elements/{name}/demo/{demo-slug}?rendering=chromeless |
| 250 | + ``` |
| 251 | +2. Open React reference: |
| 252 | + ``` |
| 253 | + new_page -> https://www.patternfly.org/components/{name}/react/{demo-slug}/ |
| 254 | + ``` |
| 255 | +3. `take_snapshot` + `take_screenshot` both tabs |
| 256 | +4. Compare visuals (layout, colors, typography, borders, dark mode) |
| 257 | +5. Compare structure (interactive elements, ARIA, keyboard nav, focus) |
| 258 | +6. Iterate: fix differences, retake, repeat until visual parity |
| 259 | +7. Interactive test: `click`, `press_key`, `fill`, check console errors |
| 260 | +8. Exhaustively check all element states e.g. expanded select, active descendant, etc |
| 261 | + |
| 262 | +### Phase 7: Write Tests |
| 263 | + |
| 264 | +Write all tests from scratch: |
| 265 | +- Unit tests validating public API (attributes, properties, events) |
| 266 | +- a11ySnapshot assertions for accessibility tree |
| 267 | +- Keyboard navigation tests |
| 268 | +- Form submission tests (if FACE) |
| 269 | +- Setup in beforeEach, assertions in it blocks |
| 270 | +- No arrow functions in Mocha tests |
| 271 | +- Test observable behavior (offsetWidth, computed styles), not shadow DOM |
| 272 | +- Cover behavioral expectations from React tests (Phase 1 step 3) |
| 273 | + which are not already covered |
| 274 | + |
| 275 | +See `.claude/skills/review-api/SKILL.md` Phase 4 for full test checklist. |
| 276 | + |
| 277 | +### Phase 8: Audit |
| 278 | + |
| 279 | +Prompt the user to activate /review-api, /review-demos, and /review-a11y |
| 280 | + |
| 281 | +Fix all critical and warning findings before PR |
| 282 | + |
| 283 | +## Quality Bar |
| 284 | + |
| 285 | +Place the web component next to React: visually and functionally |
| 286 | +indistinguishable to end users. API should feel native to web developers. |
| 287 | +Accessibility may and should exceed React's -- that's a feature, not a bug. |
0 commit comments