@times-design-system/components-wordpress 1.2.2-alpha.1 → 1.2.2-alpha.5

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.
@@ -0,0 +1,306 @@
1
+ # WordPress Block Styling Synchronization Guide
2
+
3
+ This guide ensures that WordPress block styles remain synchronized with React component styles, maintaining feature parity across implementations.
4
+
5
+ ## Overview
6
+
7
+ All WordPress blocks must have equivalent CSS to their React counterparts. This includes:
8
+
9
+ - All variant combinations (intent, size, behaviour)
10
+ - All state handlers (hover, focus, active, disabled)
11
+ - All design token references
12
+ - Identical visual appearance in patterns and editor
13
+
14
+ ## Process: React to WordPress Style Synchronization
15
+
16
+ ### Step 1: Identify React Component Styles
17
+
18
+ ```bash
19
+ # Locate React component styles
20
+ ls packages/components-react/src/<ComponentName>/
21
+ # Look for: <ComponentName>.scss or <ComponentName>.css
22
+
23
+ # Count lines to understand complexity
24
+ wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss
25
+ ```
26
+
27
+ ### Step 2: Extract Style Segments
28
+
29
+ Read the React component stylesheet and identify:
30
+
31
+ 1. **Base styles** (`.tds-<component>` class)
32
+ - Display, flex properties, gaps
33
+ - Basic colors, borders, padding
34
+ - Font properties (family, weight, size, line-height)
35
+ - Transitions and animations
36
+
37
+ 2. **Variant modifiers** (`.tds-<component>--<variant>`)
38
+ - Intent: primary, secondary, negative
39
+ - Size: small, medium, large
40
+ - Behaviour: full, hug
41
+ - Channel: on, off
42
+ - Toggle: on, off
43
+
44
+ 3. **State handlers** (pseudo-classes)
45
+ - `:hover` - hover states
46
+ - `:focus-visible` - focus states with focus ring
47
+ - `:active` - pressed states
48
+ - `:disabled` - disabled states
49
+ - `:visited` - visited states (for links)
50
+
51
+ 4. **Nested selectors** (children and pseudo-elements)
52
+ - `.tds-<component>__<child>` - nested elements
53
+ - `::after` - focus rings, decorative elements
54
+ - `::before` - pseudo-elements
55
+
56
+ ### Step 3: Copy and Adapt to WordPress
57
+
58
+ Create `style.css` in the block directory:
59
+
60
+ ```bash
61
+ cp packages/components-react/src/<ComponentName>/<ComponentName>.scss \
62
+ packages/components-wordpress/src/blocks/<component>/style-temp.css
63
+ ```
64
+
65
+ Then:
66
+
67
+ 1. Convert SCSS to CSS (remove variables, nesting, mixins)
68
+ 2. Replace any SCSS variables with CSS custom properties
69
+ 3. Ensure all color/spacing/typography use design tokens
70
+ 4. Verify BEM naming convention (hyphens, no underscores)
71
+ 5. Save as `style.css`
72
+
73
+ ### Step 4: Design Token Mapping
74
+
75
+ All style values must use CSS custom properties from `@times-design-system/theme-scss`.
76
+
77
+ **Common Design Token Categories**:
78
+
79
+ | Category | Tokens | Example |
80
+ | -------------- | -------------------------------------------------------------------- | -------------------------------------------- |
81
+ | **Colors** | `--interactive-*`, `--text-*`, `--border-*`, `--flag-*`, `--toast-*` | `var(--interactive-primary-fill-default)` |
82
+ | **Spacing** | `--spacing-01` through `--spacing-20` | `var(--spacing-03)` |
83
+ | **Typography** | `--fontFamily*`, `--fontWeight*`, `--fontSize*`, `--fontLineHeight*` | `var(--fontFamily040)`, `var(--fontSize030)` |
84
+ | **Borders** | `--border-radius-*` | `var(--border-radius-full)` |
85
+ | **Effects** | `--focus-border`, `--shadow-*` | `var(--focus-border)` |
86
+
87
+ **Validation**:
88
+
89
+ ```bash
90
+ # Check for hardcoded hex colors (should be 0 results)
91
+ grep -E '#[0-9a-fA-F]{6}' packages/components-wordpress/src/blocks/<component>/style.css
92
+
93
+ # Check for hardcoded values (should use variables)
94
+ grep -E '[0-9]+px|[0-9]+rem' packages/components-wordpress/src/blocks/<component>/style.css | grep -v 'var('
95
+ ```
96
+
97
+ ### Step 5: Variant Coverage
98
+
99
+ Ensure **all** variant combinations are styled:
100
+
101
+ **Button Example**:
102
+
103
+ ```
104
+ Intents: --intent-primary ✓, --intent-secondary ✓, --intent-negative ✓
105
+ Sizes: --size-small ✓, --size-medium ✓, --size-large ✓
106
+ Behaviour: --behaviour-full ✓, --behaviour-hug ✓
107
+ States: :hover ✓, :focus-visible ✓, :active ✓, :disabled ✓
108
+ Focus rings: ::after with --focus-border ✓
109
+ ```
110
+
111
+ **Link Example**:
112
+
113
+ ```
114
+ Intents: --intent-primary ✓, --intent-secondary ✓
115
+ Sentiments: --sentiment-brand ✓, --sentiment-utility ✓
116
+ Sizes: --size-xsmall ✓, --size-small ✓, --size-medium ✓, --size-large ✓
117
+ States: :hover ✓, :focus-visible ✓, :active ✓, :visited ✓, :disabled ✓
118
+ ```
119
+
120
+ **Chip Example**:
121
+
122
+ ```
123
+ Intents: --intent-primary ✓ (on/off variants)
124
+ Channels: --channel-on ✓, --channel-off ✓
125
+ Sizes: --size-small ✓, --size-large ✓
126
+ States: :hover ✓, :focus ✓, :active ✓, :disabled ✓
127
+ Toggle: on state (bg-dark) ✓, off state (bg-light) ✓
128
+ ```
129
+
130
+ ### Step 6: Test for Feature Parity
131
+
132
+ ```bash
133
+ # 1. Line count comparison
134
+ echo "React component:"
135
+ wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss
136
+ echo "WordPress block:"
137
+ wc -l packages/components-wordpress/src/blocks/<component>/style.css
138
+
139
+ # Acceptable difference: ±20% (accounting for SCSS nesting/mixins vs flat CSS)
140
+ # If WordPress is <50% of React, styles are likely incomplete
141
+
142
+ # 2. Variant count comparison
143
+ echo "React variants:"
144
+ grep -o '\.[a-z-]*--[a-z-]*' packages/components-react/src/<ComponentName>/<ComponentName>.scss | sort -u | wc -l
145
+ echo "WordPress variants:"
146
+ grep -o '\.[a-z-]*--[a-z-]*' packages/components-wordpress/src/blocks/<component>/style.css | sort -u | wc -l
147
+
148
+ # 3. Visual comparison
149
+ # - Open React Storybook: packages/components-react (npm run storybook)
150
+ # - Open WordPress block in editor: packages/components-wordpress
151
+ # - Compare side-by-side for each variant
152
+ # - Verify colors match exactly (CSS custom properties should be identical)
153
+ ```
154
+
155
+ ## Style Structure Template
156
+
157
+ Use this template as a reference for comprehensive block styling:
158
+
159
+ ```css
160
+ /* Base component styles */
161
+ .tds-<component > {
162
+ --tds-<component>-bg: var(--interactive-primary-fill-default);
163
+ --tds-<component>-fg: var(--interactive-primary-text-default);
164
+ --tds-<component>-border: var(--interactive-primary-border-default);
165
+
166
+ display: flex; /* or block, grid, etc. */
167
+ align-items: center;
168
+ justify-content: center;
169
+ gap: var(--spacing-03);
170
+
171
+ border: 1px solid var(--tds-<component>-border);
172
+ background-color: var(--tds-<component>-bg);
173
+ color: var(--tds-<component>-fg);
174
+
175
+ font-family: var(--fontFamily040);
176
+ font-weight: var(--fontWeight050);
177
+ font-size: var(--fontSize030);
178
+ line-height: var(--fontLineHeight040);
179
+
180
+ border-radius: 0;
181
+ transition:
182
+ background-color 0.15s ease,
183
+ border-color 0.15s ease,
184
+ color 0.15s ease;
185
+ cursor: pointer;
186
+ }
187
+
188
+ /* Intent variants */
189
+ .tds-<component > --intent-primary {
190
+ --tds-<component>-bg: var(--interactive-primary-fill-default);
191
+ --tds-<component>-fg: var(--interactive-primary-text-default);
192
+ --tds-<component>-border: var(--interactive-primary-border-default);
193
+ }
194
+
195
+ .tds-<component > --intent-secondary {
196
+ --tds-<component>-bg: var(--interactive-secondary-fill-default);
197
+ --tds-<component>-fg: var(--interactive-secondary-text-default);
198
+ --tds-<component>-border: var(--interactive-secondary-border-default);
199
+ }
200
+
201
+ /* Size variants */
202
+ .tds-<component > --size-small {
203
+ min-height: var(--spacing-15);
204
+ padding: var(--spacing-03) var(--spacing-05);
205
+ font-size: var(--fontSize020);
206
+ }
207
+
208
+ .tds-<component > --size-large {
209
+ min-height: var(--spacing-20);
210
+ padding: var(--spacing-07) var(--spacing-11);
211
+ font-size: var(--fontSize040);
212
+ }
213
+
214
+ /* Behaviour variants */
215
+ .tds-<component > --behaviour-full {
216
+ width: 100%;
217
+ }
218
+
219
+ /* State handlers */
220
+ .tds-<component > :hover:not(:disabled) {
221
+ background-color: var(--interactive-primary-fill-hover);
222
+ border-color: var(--interactive-primary-border-hover);
223
+ color: var(--interactive-primary-text-hover);
224
+ }
225
+
226
+ .tds-<component > :active:not(:disabled) {
227
+ background-color: var(--interactive-primary-fill-pressed);
228
+ border-color: var(--interactive-primary-border-pressed);
229
+ color: var(--interactive-primary-text-pressed);
230
+ }
231
+
232
+ .tds-<component > :focus-visible {
233
+ outline: none;
234
+ }
235
+
236
+ .tds-<component > :focus-visible::after {
237
+ content: '';
238
+ position: absolute;
239
+ inset: calc(var(--spacing-03) * -1);
240
+ border: var(--spacing-01) solid var(--focus-border);
241
+ border-radius: inherit;
242
+ pointer-events: none;
243
+ }
244
+
245
+ .tds-<component > :disabled {
246
+ background-color: var(--interactive-disabled-b);
247
+ border-color: transparent;
248
+ color: var(--text-disabled);
249
+ cursor: not-allowed;
250
+ opacity: 0.5;
251
+ }
252
+
253
+ /* Nested elements */
254
+ .tds-<component > __label {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ gap: var(--spacing-03);
258
+ }
259
+ ```
260
+
261
+ ## Build and Verification
262
+
263
+ ```bash
264
+ cd packages/components-wordpress
265
+
266
+ # Build the package
267
+ npm run build
268
+
269
+ # Verify styles compiled correctly
270
+ wc -l dist/blocks/<component>/style.css
271
+
272
+ # Test in WordPress
273
+ # 1. Copy dist/ to WordPress installation
274
+ # 2. Activate plugin or install NPM package
275
+ # 3. Add block to page
276
+ # 4. Test each variant combination
277
+ # 5. Test in patterns (server-side rendering)
278
+ ```
279
+
280
+ ## Maintenance
281
+
282
+ When React component styles change:
283
+
284
+ 1. **Identify the change**: What CSS properties, variants, or states changed?
285
+ 2. **Update React component**: Make the change in `packages/components-react/src/<ComponentName>/<ComponentName>.scss`
286
+ 3. **Sync to WordPress**: Update `packages/components-wordpress/src/blocks/<component>/style.css` with the same change
287
+ 4. **Build and test**: `npm run build` in both packages and test in WordPress
288
+ 5. **Document the change**: Update CHANGELOG.md in both packages
289
+
290
+ ## Common Styling Issues
291
+
292
+ | Issue | Cause | Solution |
293
+ | -------------------------------- | -------------------------------------- | --------------------------------------------------------------- |
294
+ | Block renders but styles missing | CSS not linked in `block.json` | Verify `"style": "file:./style.css"` in block.json |
295
+ | Colors don't match React | Hardcoded hex colors or wrong token | Replace with `var(--design-token)` from design system |
296
+ | Variants not styling | Modifier CSS not written | Compare to React, copy missing `.tds-<component>--*` selectors |
297
+ | Focus ring not visible | Missing `::after` pseudo-element | Add focus-visible handler with border and `var(--focus-border)` |
298
+ | Styles different in patterns | render.php classes don't match save.js | Verify class names exactly match between save.js and render.php |
299
+ | Line count too low | Styles incomplete or too minimal | Compare to React line count, should be similar (±20%) |
300
+
301
+ ## Related Documentation
302
+
303
+ - [BLOCK_CREATION_CHECKLIST.md](./BLOCK_CREATION_CHECKLIST.md) - Step-by-step checklist for creating new blocks
304
+ - [TRANSFORMATION_GUIDE.md](./TRANSFORMATION_GUIDE.md) - Complete transformation process documentation
305
+ - Design tokens: `packages/tokens/README.md`
306
+ - Component React source: `packages/components-react/src/`
@@ -263,36 +263,180 @@ export default function Save({ attributes }) {
263
263
  - No event handlers or dynamic behavior (it's static HTML)
264
264
  - Can be PHP or JavaScript-rendered depending on config
265
265
 
266
- ### 7. **Create CSS Files**
266
+ ### 7. **Create CSS Files — Full Parity with React**
267
267
 
268
- #### `style.css` (Frontend Styles)
268
+ #### `style.css` (Frontend Styles) — Critical for Feature Parity
269
269
 
270
- - Imported on both editor and frontend
271
- - Contains production CSS for the component
272
- - References SCSS classes from `theme-scss` package
273
- - Use CSS variables for tokens
270
+ The WordPress block's `style.css` **must mirror the React component's styles exactly**. This ensures visual and behavioral consistency across all implementations.
274
271
 
275
- **Template**:
272
+ **Process**:
273
+
274
+ 1. **Copy React component styles** from `packages/components-react/src/<ComponentName>/<ComponentName>.scss`
275
+ 2. **Map all className modifiers** to CSS selectors (e.g., React `className="tds-button--size-small"` → CSS `.tds-button--size-small { ... }`)
276
+ 3. **Preserve all CSS custom properties** for design tokens
277
+ 4. **Include all state handlers**: hover, focus-visible, active, disabled
278
+ 5. **Include all variants**: intent (primary, secondary, negative), size (small, medium, large), behaviour (full, hug)
279
+
280
+ **Key Principles**:
281
+
282
+ - Use **resolved design token values** for ALL properties: actual hex colors (e.g., `#005c8a`), pixel sizes (e.g., `8px`), and font names (e.g., `Roboto`)
283
+ - Follow BEM naming: `.tds-<component>`, `.tds-<component>--<modifier>`
284
+ - Include transitions for smooth state changes (usually `0.15s ease`)
285
+ - Focus rings should use concrete colors (e.g., `#737373`) with box-shadow pseudo-element
286
+ - Disabled states need opacity reduction and cursor change
287
+ - All pseudo-classes (`:hover`, `:focus-visible`, `:active`, `:disabled`) must match React behavior
288
+
289
+ **Example Structure** (from Button component):
276
290
 
277
291
  ```css
278
- .tds-<component > -wrapper {
279
- /* Component wrapper styles */
292
+ .tds-button {
293
+ --tds-button-bg: var(--interactive-primary-fill-default);
294
+ --tds-button-fg: var(--interactive-primary-text-default);
295
+ --tds-button-border: var(--interactive-primary-border-default);
296
+
297
+ display: flex;
298
+ align-items: center;
299
+ gap: var(--spacing-03);
300
+ border: 1px solid var(--tds-button-border);
301
+ background-color: var(--tds-button-bg);
302
+ color: var(--tds-button-fg);
303
+ font-family: Roboto;
304
+ font-weight: 500;
305
+ transition: 0.15s ease;
306
+ cursor: pointer;
307
+ }
308
+
309
+ /* Intent variants */
310
+ .tds-button--intent-primary {
311
+ --tds-button-bg: var(--interactive-primary-fill-default);
312
+ --tds-button-fg: var(--interactive-primary-text-default);
313
+ }
314
+
315
+ .tds-button--intent-secondary {
316
+ --tds-button-bg: var(--interactive-secondary-fill-default);
317
+ --tds-button-fg: var(--interactive-secondary-text-default);
318
+ }
319
+
320
+ /* Size variants — RESOLVED values */
321
+ .tds-button--size-small {
322
+ min-height: 40px;
323
+ padding: 4px 8px;
324
+ font-size: 1.4rem;
325
+ }
326
+
327
+ .tds-button--size-medium {
328
+ min-height: 48px;
329
+ padding: 8px 12px;
330
+ font-size: 1.6rem;
331
+ }
332
+
333
+ .tds-button--size-large {
334
+ min-height: 64px;
335
+ padding: 12px 20px;
336
+ font-size: 1.8rem;
337
+ }
338
+
339
+ /* State handlers */
340
+ .tds-button:hover:not(:disabled) {
341
+ background-color: var(--interactive-primary-fill-hover);
342
+ border-color: var(--interactive-primary-border-hover);
343
+ }
344
+
345
+ .tds-button:focus-visible {
346
+ outline: none;
280
347
  }
281
348
 
282
- .tds-<component > {
283
- /* Component styles - mostly inherited from theme-scss */
349
+ .tds-button:focus-visible::after {
350
+ content: '';
351
+ position: absolute;
352
+ inset: calc(var(--spacing-03) * -1);
353
+ border: var(--spacing-01) solid var(--focus-border);
354
+ pointer-events: none;
284
355
  }
285
356
 
286
- .tds-<component > --full {
357
+ .tds-button:disabled {
358
+ background-color: var(--interactive-disabled-b);
359
+ color: var(--text-disabled);
360
+ cursor: not-allowed;
361
+ opacity: 0.5;
362
+ }
363
+
364
+ /* Behaviour variants */
365
+ .tds-button--behaviour-full {
287
366
  width: 100%;
288
367
  }
289
368
  ```
290
369
 
291
- #### `style-editor.css` (Editor Styles)
370
+ **Validation Checklist**:
371
+
372
+ ✓ All CSS custom properties match design tokens
373
+ ✓ BEM naming is consistent (no underscores, only hyphens)
374
+ ✓ All variant combinations present (intent × size × behaviour)
375
+ ✓ State handlers for hover, focus, active, disabled
376
+ ✓ Line count similar to React component (indicates parity)
377
+ ✓ Transitions preserved
378
+ ✓ No hardcoded hex colors (all use CSS variables)
379
+
380
+ #### `style-editor.css` (Editor Styles) — Optional
292
381
 
293
382
  - Only loaded in WordPress editor
294
383
  - Can override styles for better editing experience
295
384
  - Optional — only add if editor preview differs significantly from frontend
385
+ - Example: Adjust padding/margins for better visual feedback in editor
386
+ - Example: Add borders to clarify block boundaries during editing
387
+
388
+ #### CSS Variables and theme-scss Integration
389
+
390
+ **IMPORTANT**: WordPress blocks use **resolved design token values** — actual hex colors, pixel measurements, and font definitions are compiled directly into the CSS. This means blocks are completely self-contained and do not depend on CSS custom properties from theme-scss.
391
+
392
+ **Key Approach**:
393
+
394
+ ```
395
+ Design Tokens (theme-scss)
396
+
397
+ Resolved to concrete values
398
+
399
+ Hardcoded into WordPress block CSS
400
+
401
+ No runtime CSS variable dependencies needed
402
+ ```
403
+
404
+ **Why Resolved Values**:
405
+
406
+ - Blocks work reliably in any WordPress theme context
407
+ - No need for theme-scss CSS variables to be loaded
408
+ - Consistent appearance across installations
409
+ - Simpler debugging (actual colors/sizes visible in DevTools)
410
+
411
+ **Value Reference**:
412
+
413
+ | Value Type | Examples | Notes |
414
+ | ----------------- | ----------------------------------------------- | ------------------------------------- |
415
+ | **Colors (hex)** | `#005c8a`, `#ffffff`, `#737373` | All interactive colors use hex values |
416
+ | **Spacing (px)** | `4px`, `8px`, `12px`, `16px`, `24px` | All spacing uses pixel units |
417
+ | **Typography** | `-apple-system, BlinkMacSystemFont, "Segoe UI"` | System fonts specified directly |
418
+ | **Font weight** | `400`, `500`, `700` | Numeric weight values |
419
+ | **Font size** | `0.75rem`, `1rem`, `1.125rem` | REMs for scalability |
420
+ | **Border radius** | `0`, `2px`, `4px`, `9999px` | Explicit pixel or full values |
421
+
422
+ **Example Block CSS** (resolved values):
423
+
424
+ ```css
425
+ .tds-button {
426
+ /* NO CSS variables — all values are concrete */
427
+ --tds-button-bg: #005c8a; /* Direct hex, not var(--color-*) */
428
+ --tds-button-fg: #ffffff; /* No fallbacks needed */
429
+
430
+ gap: 4px; /* Direct pixel, not var(--spacing-03) */
431
+ padding: 8px 12px; /* Resolved from design tokens */
432
+
433
+ font-family: Roboto;
434
+ font-weight: 500; /* Direct numeric value */
435
+ font-size: 1.6rem; /* Direct resolved size from theme-scss */
436
+ }
437
+ ```
438
+
439
+ **See Also**: [SCSS_VARIABLES_REFERENCE.md](./SCSS_VARIABLES_REFERENCE.md) for complete mapping of all resolved values used across all blocks.
296
440
 
297
441
  ### 8. **Create/Update Utility Functions**
298
442
 
@@ -343,6 +487,32 @@ import './blocks/button/index.js';
343
487
  import './blocks/<new-component>/index.js';
344
488
  ```
345
489
 
490
+ ### 10. **Build and Verify**
491
+
492
+ **After completing all files**:
493
+
494
+ ```bash
495
+ # Build the package
496
+ cd packages/components-wordpress
497
+ npm run build
498
+
499
+ # Verify block appears in dist/ with all styles
500
+ ls dist/blocks/<new-component>/
501
+ # Expected: block.json, index.js, edit.js, save.js, render.php, style.css, style-editor.css
502
+
503
+ # Check line count of compiled style.css (should be substantial)
504
+ wc -l dist/blocks/<new-component>/style.css
505
+ ```
506
+
507
+ **Build verification checklist**:
508
+
509
+ ✓ `dist/blocks/<new-component>/render.php` exists (server-side rendering)
510
+ ✓ `dist/blocks/<new-component>/style.css` contains all styles (50+ lines for complex components)
511
+ ✓ `block.json` has `"render": "file:./render.php"` reference
512
+ ✓ No TypeScript/build errors
513
+ ✓ Block registrations all successful
514
+ ✓ CSS custom properties properly compiled
515
+
346
516
  ---
347
517
 
348
518
  ## Prop-to-Attribute Conversion Reference
@@ -593,6 +763,97 @@ Suggested order for consistent, dependency-aware transformation:
593
763
 
594
764
  ---
595
765
 
766
+ ## Testing & Validation Checklist
767
+
768
+ ### Pre-Build Testing
769
+
770
+ - [ ] **Styling Parity**: Run visual comparison of React component vs WordPress block
771
+ - Open React component in Storybook: `packages/components-react/src/<ComponentName>`
772
+ - Compare all variants side-by-side (intent, size, behaviour)
773
+ - Verify colors match exactly (should be identical CSS custom properties)
774
+ - Check spacing and padding are correct
775
+ - Test state transitions (hover, focus, active, disabled)
776
+
777
+ - [ ] **CSS Validation**:
778
+ - Line count check: `wc -l src/blocks/<component>/style.css` — should be substantial (50+ for complex components like Button/Link, 20+ for simple components)
779
+ - Compare to React component: `wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss`
780
+ - Should be similar complexity (±20% acceptable difference)
781
+ - No hardcoded hex colors: `grep -E '#[0-9a-fA-F]{6}' src/blocks/<component>/style.css` — should be zero results (all colors use CSS variables)
782
+ - Validate JSON: `npm run test`
783
+
784
+ - [ ] **Render Callback Testing**:
785
+ - Verify `render.php` exists for server-side pattern rendering
786
+ - Test in WordPress patterns: create pattern with block variants
787
+ - Confirm HTML output matches `save.js` structure exactly
788
+ - Test with different attribute combinations in patterns
789
+
790
+ ### Post-Build Testing
791
+
792
+ ```bash
793
+ # Navigate to packages/components-wordpress
794
+ cd packages/components-wordpress
795
+
796
+ # Run build
797
+ npm run build
798
+
799
+ # Verify compiled styles
800
+ wc -l dist/blocks/<component>/style.css # Check line count (should match src/)
801
+ grep -c 'tds-<component>--' dist/blocks/<component>/style.css # Count modifiers
802
+
803
+ # Verify render.php exists
804
+ ls dist/blocks/<component>/render.php
805
+ ```
806
+
807
+ - [ ] **Compiled Output**:
808
+ - All variant CSS is present in `dist/blocks/<component>/style.css`
809
+ - `render.php` file exists and is copied to dist
810
+ - `block.json` contains `"render": "file:./render.php"`
811
+
812
+ - [ ] **WordPress Installation Testing**:
813
+ 1. Build package: `npm run build`
814
+ 2. Copy `dist/` to WordPress installation
815
+ 3. Activate plugin or install NPM package
816
+ 4. Add block to page in editor
817
+ 5. Test each variant combination:
818
+ - Different intents (primary, secondary, negative)
819
+ - Different sizes (small, medium, large)
820
+ - Different states (hover by mouse, focus by keyboard, active by click, disabled)
821
+ 6. Test in patterns (server-side rendering):
822
+ - Create pattern with block variants
823
+ - Confirm HTML renders correctly before editor JS loads
824
+ - Verify CSS loads properly
825
+ 7. Test responsive breakpoints (if applicable):
826
+ - Resize browser window
827
+ - Verify layout adapts correctly
828
+ 8. Test accessibility:
829
+ - Keyboard navigation through interactive elements
830
+ - Screen reader announcements
831
+ - Focus ring visibility
832
+
833
+ ### Styling Feature Parity Validation
834
+
835
+ For components with variants (Button, Link, Chip, etc.), verify complete coverage:
836
+
837
+ ```
838
+ Component: Button
839
+ Intents: primary ✓, secondary ✓, negative ✓
840
+ Sizes: small ✓, medium ✓, large ✓
841
+ Behaviour: hug ✓, full ✓
842
+ States: base ✓, hover ✓, focus ✓, active ✓, disabled ✓
843
+ CSS Classes: .tds-button--intent-primary ✓, .tds-button--size-small ✓, etc.
844
+ ```
845
+
846
+ - [ ] All intent variants styled (must match React exactly)
847
+ - [ ] All size variants styled (must match React exactly)
848
+ - [ ] All behaviour variants styled (must match React exactly)
849
+ - [ ] All state handlers present (hover, focus-visible, active, disabled)
850
+ - [ ] CSS custom properties for all tokens (colors, spacing, typography)
851
+ - [ ] Focus rings use consistent `var(--focus-border)`
852
+ - [ ] Transitions preserved from React component
853
+ - [ ] No visual regressions compared to React
854
+
855
+ ---
856
+
596
857
  ## Debugging & Troubleshooting
597
858
 
598
859
  ### Block not appearing in Gutenberg?
@@ -607,6 +868,10 @@ Suggested order for consistent, dependency-aware transformation:
607
868
  1. Verify CSS files in `block.json` point to correct paths
608
869
  2. Check class names match SCSS in `theme-scss`
609
870
  3. Ensure theme-scss is installed: `npm install @times-design-system/theme-scss`
871
+ 4. **Styling gap**: Compare `style.css` line count to React component — if significantly lower, styles may be incomplete
872
+ - Check for all variant modifiers: grep `.tds-<component>--` src/blocks/<component>/style.css
873
+ - Verify state handlers (hover, focus, active, disabled) are present
874
+ - Copy missing styles from React component
610
875
 
611
876
  ### Attributes not saving?
612
877
 
@@ -614,6 +879,14 @@ Suggested order for consistent, dependency-aware transformation:
614
879
  2. Ensure `setAttributes({ key: value })` is called correctly
615
880
  3. Check attribute types match intended use
616
881
 
882
+ ### Block doesn't render in patterns?
883
+
884
+ 1. Verify `render.php` exists in block directory
885
+ 2. Check `block.json` has `"render": "file:./render.php"` reference
886
+ 3. Rebuild with `npm run build` to copy render.php to dist/
887
+ 4. Verify render.php HTML structure matches save.js exactly
888
+ 5. Test in WordPress patterns (not just editor)
889
+
617
890
  ---
618
891
 
619
892
  ## References