@schalkneethling/calavera-skill-frontend-engineering 0.2.0 → 0.2.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.
@@ -6,6 +6,6 @@
6
6
  "displayName": "Frontend engineering",
7
7
  "payload": "payload/frontend-engineering",
8
8
  "compatibility": {
9
- "calavera": ">=2.2.0 <3"
9
+ "calavera": ">=2.2.0"
10
10
  }
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schalkneethling/calavera-skill-frontend-engineering",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Frontend engineering artifact for Calavera.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,7 +13,7 @@ Use this skill as the entry point for frontend work. Start with the relevant imp
13
13
  - For CSS, Sass/Less, CSS modules, scoped styles, CSS-in-JS, layout, responsive behavior, motion, colors, focus states, or selectors, read `references/css-authoring.md` and `references/css-patterns.md`.
14
14
  - For forms, authentication, untrusted input, browser storage, URLs, HTML injection, API calls, uploads, or other security-sensitive browser flows, load the `frontend-security` skill.
15
15
  - For behavior changes, accessibility checks, visual changes, or regression coverage, load the `frontend-testing` skill.
16
- - For a new or foundational custom-property system, load the `css-tokens` skill.
16
+ - For a new or foundational custom-property system, load the `css-tokens` skill when it is installed; otherwise define the required tokens locally with explicit fallback values.
17
17
 
18
18
  ## Working Principles
19
19
 
@@ -4,7 +4,7 @@ Guidance for writing CSS that prioritizes web standards, accessibility, performa
4
4
 
5
5
  ## Core Principles
6
6
 
7
- 1. **Web standards first** — Use native CSS features before reaching for libraries or frameworks. No Tailwind, no CSS-in-JS unless explicitly requested.
7
+ 1. **Web standards first** — Prefer native CSS features for new implementations. Follow the project's established styling conventions, including Tailwind or CSS-in-JS, when they are already in use or explicitly required.
8
8
  2. **Accessibility as a requirement** — Ensure styles support, never hinder, assistive technologies. Respect user preferences (motion, color scheme, contrast).
9
9
  3. **Performance matters** — Minimize repaints, avoid layout thrashing, use efficient selectors.
10
10
  4. **Readable over clever** — Future maintainers (including the author) should understand the code at a glance.
@@ -207,8 +207,13 @@ The Shared First approach may use more lines, but results in more maintainable,
207
207
  Custom properties can reduce repetition by keeping the property declaration shared while scoping only the value to each viewport. Use sparingly — overuse can make code harder to follow.
208
208
 
209
209
  ```css
210
+ :root {
211
+ --size-16: 1rem;
212
+ --size-32: 2rem;
213
+ }
214
+
210
215
  .Dialog-container {
211
- padding-block: var(--Dialog-block-padding);
216
+ padding-block: var(--Dialog-block-padding, 1rem);
212
217
  }
213
218
 
214
219
  @media (width < 48rem) {
@@ -17,8 +17,19 @@ Make heading level a prop/parameter with a sensible default.
17
17
 
18
18
  ```jsx
19
19
  // React example
20
+ function normalizeHeadingLevel(value, fallback) {
21
+ const isSupportedValue =
22
+ typeof value === "number" || (typeof value === "string" && /^[1-6]$/.test(value));
23
+ if (!isSupportedValue) {
24
+ return fallback;
25
+ }
26
+
27
+ const candidate = Number(value);
28
+ return Number.isInteger(candidate) && candidate >= 1 && candidate <= 6 ? candidate : fallback;
29
+ }
30
+
20
31
  function Card({ title, headingLevel = 3, children }) {
21
- const safeHeadingLevel = Math.min(6, Math.max(1, Number(headingLevel) || 3));
32
+ const safeHeadingLevel = normalizeHeadingLevel(headingLevel, 3);
22
33
  const Heading = `h${safeHeadingLevel}`;
23
34
  return (
24
35
  <article className="card">
@@ -32,7 +43,7 @@ function Card({ title, headingLevel = 3, children }) {
32
43
  ```twig
33
44
  {# Twig example #}
34
45
  {% set requested_level = heading_level|default(3) %}
35
- {% set heading_tag = requested_level < 1 ? 1 : requested_level > 6 ? 6 : requested_level %}
46
+ {% set heading_tag = requested_level matches '/^[1-6]$/' ? requested_level : 3 %}
36
47
  <article class="card">
37
48
  <h{{ heading_tag }}>{{ title }}</h{{ heading_tag }}>
38
49
  {{ content }}
@@ -98,9 +109,20 @@ function CardList({ cards }) {
98
109
  Create a heading component that handles both semantic and visual concerns.
99
110
 
100
111
  ```jsx
112
+ function normalizeHeadingLevel(value, fallback) {
113
+ const isSupportedValue =
114
+ typeof value === "number" || (typeof value === "string" && /^[1-6]$/.test(value));
115
+ if (!isSupportedValue) {
116
+ return fallback;
117
+ }
118
+
119
+ const candidate = Number(value);
120
+ return Number.isInteger(candidate) && candidate >= 1 && candidate <= 6 ? candidate : fallback;
121
+ }
122
+
101
123
  function Heading({ level, visualLevel = level, children, className = "" }) {
102
- const semanticLevel = Math.min(6, Math.max(1, Number(level) || 2));
103
- const visualHeadingLevel = Math.min(6, Math.max(1, Number(visualLevel) || semanticLevel));
124
+ const semanticLevel = normalizeHeadingLevel(level, 2);
125
+ const visualHeadingLevel = normalizeHeadingLevel(visualLevel, semanticLevel);
104
126
  const Tag = `h${semanticLevel}`;
105
127
  const visualClass = `u-heading-${visualHeadingLevel}`;
106
128
 
@@ -131,8 +153,9 @@ Generic components inherit heading config when specialised.
131
153
 
132
154
  ```jsx
133
155
  // Generic card
156
+ // Reuse normalizeHeadingLevel from Pattern 1.
134
157
  function Card({ title, headingLevel = 3, headingClass, children }) {
135
- const safeHeadingLevel = Math.min(6, Math.max(1, Number(headingLevel) || 3));
158
+ const safeHeadingLevel = normalizeHeadingLevel(headingLevel, 3);
136
159
  const Heading = `h${safeHeadingLevel}`;
137
160
  return (
138
161
  <article className="card">
@@ -87,7 +87,7 @@ Visual styling and semantic meaning are related but not coupled. CSS classes bri
87
87
 
88
88
  ### Skip Navigation Links
89
89
 
90
- Skip links let keyboard and screen reader users bypass repeated navigation blocks and jump directly to meaningful content. They are required on any page with a navigation block or other repeated content before the main content.
90
+ Skip links are the recommended default for letting keyboard users bypass repeated navigation and jump directly to meaningful content. WCAG 2.4.1 requires a bypass mechanism for repeated blocks; depending on the page structure, suitable headings, landmarks, or another mechanism may also satisfy that requirement.
91
91
 
92
92
  Place skip links as the **first focusable element** in `<body>`. They can be visually hidden and revealed on focus:
93
93
 
@@ -128,17 +128,17 @@ Place skip links as the **first focusable element** in `<body>`. They can be vis
128
128
 
129
129
  Use landmark elements to convey page structure:
130
130
 
131
- | Element | Use When | Notes |
132
- | --------- | ---------------------------- | -------------------------------------------------------------------------------- |
133
- | `header` | Page or section header | Can appear multiple times in different contexts |
134
- | `footer` | Page or section footer | Contact info, copyright, related links |
135
- | `nav` | Navigation sections | Must be labelled; avoid "navigation" in the label (screen readers announce this) |
136
- | `main` | Primary content | Only one per page; must contain the primary `<h1>` |
137
- | `aside` | Tangentially related content | Content removable without changing the page's main story (sidebars, ads) |
138
- | `search` | Search functionality | Contains the search form, not the results |
139
- | `form` | User input | Only becomes a landmark when labelled via `aria-labelledby` or `aria-label` |
140
- | `article` | Self-contained content | Would make sense syndicated or standalone |
141
- | `section` | Thematic grouping | Only becomes a landmark when labelled |
131
+ | Element | Use When | Notes |
132
+ | --------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
133
+ | `header` | Page or section header | Can appear multiple times in different contexts |
134
+ | `footer` | Page or section footer | Contact info, copyright, related links |
135
+ | `nav` | Navigation sections | One may be unnamed; give multiple landmarks names that distinguish their purpose. Identical repeated navigation may share a name. Avoid "navigation" in the label because screen readers announce the role. |
136
+ | `main` | Primary content | Only one per page; must contain the primary `<h1>` |
137
+ | `aside` | Tangentially related content | Content removable without changing the page's main story (sidebars, ads) |
138
+ | `search` | Search functionality | Contains the search form, not the results |
139
+ | `form` | User input | Only becomes a landmark when labelled via `aria-labelledby` or `aria-label` |
140
+ | `article` | Self-contained content | Would make sense syndicated or standalone |
141
+ | `section` | Thematic grouping | Only becomes a landmark when labelled |
142
142
 
143
143
  #### `<main>` must contain the primary `<h1>`
144
144
 
@@ -251,27 +251,27 @@ Sometimes text looks like a heading but shouldn't be one semantically. Use CSS c
251
251
 
252
252
  ### When to Use Lists
253
253
 
254
- Lists are most useful when **knowing the number of items helps the user**:
254
+ Use a list when sibling items have a semantic relationship that should remain clear without visual styling:
255
255
 
256
- - Navigation menus (how many options?)
257
- - Search results (how many matches?)
258
- - Image galleries (how many images?)
256
+ - Navigation menus
257
+ - Search results
258
+ - Image galleries
259
259
  - Steps in a process
260
260
 
261
261
  **Questions to ask:**
262
262
 
263
263
  - Are these items genuinely peers?
264
- - Would removing one make the others feel incomplete?
265
- - Is there an implicit "here are N things" being communicated?
264
+ - Does the order or ranking carry meaning?
265
+ - Are these term-description pairs rather than peer items?
266
266
 
267
267
  ### List Types
268
268
 
269
- | Type | Use When | Example |
270
- | ------ | ---------------------------------------- | ------------------------------------- |
271
- | `ul` | Unordered collection where count matters | Nav items, search results |
272
- | `ol` | Sequential steps or ranked items | Recipes, instructions, top-10 lists |
273
- | `dl` | Term-description pairs | Glossaries, metadata, key-value pairs |
274
- | `menu` | Toolbar commands | Action buttons, not navigation |
269
+ | Type | Use When | Example |
270
+ | ------ | ---------------------------------------------- | ------------------------------------- |
271
+ | `ul` | Related peer items without meaningful sequence | Nav items, search results |
272
+ | `ol` | Items whose sequence or ranking matters | Recipes, instructions, top-10 lists |
273
+ | `dl` | Term-description pairs | Glossaries, metadata, key-value pairs |
274
+ | `menu` | Toolbar commands | Action buttons, not navigation |
275
275
 
276
276
  **Ordered list attributes:** Use `reversed` for countdown-style lists (e.g., a top 10 listed from 10 to 1). Use `start` to begin numbering from a specific value. Both are native HTML—no JavaScript required.
277
277
 
@@ -287,7 +287,7 @@ Note: A single `dt` can have multiple `dd` elements for multiple related descrip
287
287
 
288
288
  ### Decorative List Separators
289
289
 
290
- When using CSS `::before` or `::after` to inject visual separators (e.g., breadcrumb `›`), browsers automatically exclude generated content from the accessibility tree—no extra markup is required. Do **not** try to hide it with `aria-hidden: "true"` as a CSS property; that is invalid and has no effect. If injecting separators via HTML (not CSS), use `<span aria-hidden="true">` on the HTML element.
290
+ Keep visual separators decorative. Prefer borders or other non-content styling because CSS-generated `::before` and `::after` content can be exposed inconsistently across browser and assistive-technology combinations. If generated content is necessary, test the supported combinations. Do **not** try to hide it with `aria-hidden: "true"` as a CSS property; that is invalid and has no effect. If injecting separators via HTML, use `<span aria-hidden="true">` on the element.
291
291
 
292
292
  ## Interactive Elements
293
293
 
@@ -410,20 +410,22 @@ Legends can be visually hidden while still providing accessible names.
410
410
  ```html
411
411
  <!-- Correct: all filter controls share a single search landmark -->
412
412
  <search aria-label="Filter employees">
413
- <label for="q">Search</label>
414
- <input type="search" id="q" name="q" />
415
-
416
- <label for="dept">Department</label>
417
- <select id="dept" name="dept">
418
- ...
419
- </select>
420
-
421
- <label for="status">Status</label>
422
- <select id="status" name="status">
423
- ...
424
- </select>
425
-
426
- <button type="submit">Apply filters</button>
413
+ <form action="/employees" method="get">
414
+ <label for="q">Search</label>
415
+ <input type="search" id="q" name="q" />
416
+
417
+ <label for="dept">Department</label>
418
+ <select id="dept" name="dept">
419
+ ...
420
+ </select>
421
+
422
+ <label for="status">Status</label>
423
+ <select id="status" name="status">
424
+ ...
425
+ </select>
426
+
427
+ <button type="submit">Apply filters</button>
428
+ </form>
427
429
  </search>
428
430
 
429
431
  <!-- Wrong: only the text input is wrapped -->
@@ -531,7 +533,7 @@ In order of preference:
531
533
  2. **Horizontal scroll** — Preserves semantics but may challenge users with motor difficulties
532
534
  3. **Component duplication (cards on mobile)** — Last resort; maintain accessibility in both versions
533
535
 
534
- Note: Modern browsers (including Safari) no longer strip table semantics when applying `display: grid` or `display: flex`, opening new responsive possibilities.
536
+ Keep the table's native display and put overflow on a wrapper. Changing table elements to `display: grid` or `display: flex` can remove native table semantics in some browser and assistive-technology combinations; rely on altered display semantics only after testing every supported combination.
535
537
 
536
538
  ## Code Review Checklist
537
539