@schalkneethling/calavera-skill-frontend-engineering 0.2.0 → 0.2.1
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.
- package/package.json +1 -1
- package/payload/frontend-engineering/SKILL.md +1 -1
- package/payload/frontend-engineering/references/css-authoring.md +1 -1
- package/payload/frontend-engineering/references/css-patterns.md +6 -1
- package/payload/frontend-engineering/references/semantic-html-heading-patterns.md +28 -5
- package/payload/frontend-engineering/references/semantic-html.md +42 -40
package/package.json
CHANGED
|
@@ -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** —
|
|
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 =
|
|
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
|
|
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 =
|
|
103
|
-
const visualHeadingLevel =
|
|
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 =
|
|
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
|
|
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 |
|
|
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
|
-
|
|
254
|
+
Use a list when sibling items have a semantic relationship that should remain clear without visual styling:
|
|
255
255
|
|
|
256
|
-
- Navigation menus
|
|
257
|
-
- Search results
|
|
258
|
-
- Image galleries
|
|
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
|
-
-
|
|
265
|
-
-
|
|
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
|
|
270
|
-
| ------ |
|
|
271
|
-
| `ul` |
|
|
272
|
-
| `ol` |
|
|
273
|
-
| `dl` | Term-description pairs
|
|
274
|
-
| `menu` | Toolbar commands
|
|
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
|
-
|
|
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
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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
|
|