Skip to content

Commit 19c486a

Browse files
docs(skill): V5-V9 fixes — CSS import removal, button size, icon corrections, token compliance
Syncs all skill improvements from V5 through V9: - Remove nonexistent `dist/styles.css` import (f36 v6 uses Emotion) - Standardize all examples to `style` prop over Emotion `css` prop - Add `size="small"` to every Button/IconButton (32px standard, never 40px) - Fix 38 wrong icon names to Phosphor convention - Fix shadow token names, add HouseIcon, correct CloseCircleIcon → WarningOctagonIcon - Replace hardcoded padding with f36 tokens, add token compliance exceptions - Add app-shell.tsx example with Navbar, sidebar, and content area - Rename confirmation-dialog → confirmation-modal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6f687bf commit 19c486a

12 files changed

Lines changed: 295 additions & 57 deletions

File tree

skills/SKILL.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ Forma 36 is the design language of Contentful. Every screen, component, and inte
2222
## Pre-flight checklist — VERIFY before generating ANY code
2323

2424
```
25-
REQUIRED import '@contentful/f36-components/dist/styles.css' — without this, all components render unstyled
25+
REQUIRED f36 v6 is self-styling via Emotion — NO CSS import needed. Do NOT add `import '@contentful/f36-components/dist/styles.css'` — that file does not exist and will break the build.
2626
REQUIRED Every component has an explicit import from @contentful/f36-components or @contentful/f36-icons
27-
REQUIRED All icon names come from @contentful/f36-icons (Phosphor set) — see guidelines/foundations/icons.md for the approved list. NEVER invent icon names. Common: DotsThreeIcon (menus), TrashIcon (delete), PencilIcon (edit), GearIcon (settings), PlusIcon, SearchIcon, CloseIcon, CheckIcon, WarningIcon, ArrowLeftIcon.
27+
REQUIRED All icon names come from @contentful/f36-icons (Phosphor set) — see guidelines/foundations/icons.md for the approved list. NEVER invent icon names. Common: DotsThreeIcon (menus), TrashSimpleIcon (delete), PencilSimpleIcon (edit), GearSixIcon (settings), PlusIcon, MagnifyingGlassIcon (search), XIcon (close), CheckIcon, WarningIcon, ArrowLeftIcon.
2828
REQUIRED All colors, spacing, typography use @contentful/f36-tokens — NEVER hardcode hex, px, or font stacks
2929
REQUIRED Use Layout (not Workbench) as the page shell. Layout.Header, Layout.Body, Layout.Sidebar are REAL compound sub-components (verified in source) — use them confidently. These are the ONLY valid sub-components.
30-
REQUIRED Skeleton sub-components: Skeleton.Container, Skeleton.DisplayText, Skeleton.BodyText, Skeleton.Image — these are the ONLY valid sub-components. There is NO Skeleton.Row.
30+
REQUIRED Skeleton sub-components: Skeleton.Container, Skeleton.DisplayText, Skeleton.BodyText, Skeleton.Image, Skeleton.Row — these are the ONLY valid sub-components. Use Skeleton.Row inside Table.Body for table loading states (props: rowCount, columnCount).
3131
```
3232

3333
---
@@ -136,7 +136,7 @@ See `guidelines/composition/visual-verification.md` for the detailed checklist.
136136

137137
These apply to all paths. Do not override them.
138138

139-
- **CSS import is REQUIRED**every file must include `import '@contentful/f36-components/dist/styles.css';`. Without it, all components render unstyled. This is the #1 most-forgotten rule.
139+
- **No CSS import needed**f36 v6 uses Emotion for runtime styling. `@contentful/f36-components/dist/styles.css` does not exist. Do NOT add it — it will cause a build error.
140140
- **Tokens only** — never hardcode hex colors, pixel values, or font stacks. Always use `@contentful/f36-tokens`.
141141
- **Sentence case** — all labels, headings, buttons, menu items. Never Title Case or ALL CAPS.
142142
- **One primary button** per screen section maximum.
@@ -148,6 +148,7 @@ These apply to all paths. Do not override them.
148148
- **`Layout` not `Workbench`** — Workbench is deprecated.
149149
- **No custom shadows** — only `shadowDefault`, `shadowHeavy`, `shadowButton`, `insetBoxShadowDefault`.
150150
- **No border radius outside the scale** — 4px, 6px, 12px, 100px only.
151+
- **`Button size="small"` always** — the default 32px height is standard. Never use medium (40px).
151152

152153
---
153154

@@ -161,21 +162,18 @@ From the [Forma 36 code style guide](https://github.com/contentful/forma-36/blob
161162
- Component type names use `ComponentNameProps` pattern
162163
- Use `camelCase` for all prop names
163164
- Pass string prop values with double quotes directly, not wrapped in `{}`
165+
- Use `style` prop for inline styles — it works universally. The `css` prop (Emotion) only works if the host app has Emotion configured (f36 uses it internally but doesn't expose it to consumers).
164166
- Prefer `className` over inline `style` unless manipulating dynamic values
165167

166168
---
167169

168170
## Dependencies
169171

170172
```bash
171-
npm install @contentful/f36-components @contentful/f36-tokens @contentful/f36-icons
173+
npm install @contentful/f36-components @contentful/f36-tokens @contentful/f36-icons @contentful/f36-navbar
172174
```
173175

174-
Requires React 18. Compatible with Vite. CSS import is mandatory:
175-
176-
```tsx
177-
import '@contentful/f36-components/dist/styles.css';
178-
```
176+
Requires React 18. Compatible with Vite. f36 v6 is self-styling via Emotion — no CSS file import needed.
179177

180178
Font: Geist from `https://cdn.f36.contentful.com`. Apply `font-feature-settings: 'ss05' 1`.
181179

@@ -254,27 +252,29 @@ Each folder contains an `overview.md` that indexes its contents.
254252

255253
Working TSX files demonstrating correct usage. Each maps to a screen pattern.
256254

257-
| File | Pattern | What it shows |
258-
| ------------------------- | --------------- | ------------------------------------------------------------------------------------ |
259-
| `list-page.tsx` | List / Index | Table, search, EntityStatusBadge, Menu actions, Pagination, empty state, Skeleton |
260-
| `detail-page.tsx` | Detail / Edit | Right sidebar, breadcrumbs, form validation, unsaved-changes Note |
261-
| `settings-page.tsx` | Settings | Grouped sections, Switch toggles, danger zone |
262-
| `confirmation-dialog.tsx` | Confirmation | Modal, "Never mind" cancel, `isLoading` confirm, `shouldCloseOnOverlayClick={false}` |
263-
| `form-page.tsx` | Standalone Form | `Layout variant="narrow"`, grouped FormControls, Accordion, Notification |
264-
| `error-state.tsx` | Error State | Centered error, WarningOctagonIcon, "Try again" + "Go back" |
255+
| File | Pattern | What it shows |
256+
| ------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
257+
| `app-shell.tsx` | Full app chrome | Navbar (from f36-navbar), sidebar (280px, nav items, sections), content area — the outer wrapper most pages sit inside |
258+
| `list-page.tsx` | List / Index | Table, search, EntityStatusBadge, Menu actions, Pagination, empty state, Skeleton |
259+
| `detail-page.tsx` | Detail / Edit | Right sidebar, breadcrumbs, form validation, unsaved-changes Note |
260+
| `settings-page.tsx` | Settings | Grouped sections, Switch toggles, danger zone |
261+
| `confirmation-dialog.tsx` | Confirmation | Modal, "Never mind" cancel, `isLoading` confirm, `shouldCloseOnOverlayClick={false}` |
262+
| `form-page.tsx` | Standalone Form | `Layout variant="narrow"`, grouped FormControls, Accordion, Notification |
263+
| `error-state.tsx` | Error State | Centered error, WarningOctagonIcon, "Try again" + "Go back" |
265264

266265
---
267266

268267
## Post-flight checklist — VERIFY before returning code to the user
269268

270269
```
271-
CHECK CSS import present: import '@contentful/f36-components/dist/styles.css'
272-
CHECK Every color, spacing, and font value comes from @contentful/f36-tokens — no hardcoded hex, px, or font stacks
270+
CHECK No CSS import f36 v6 is self-styling via Emotion. `dist/styles.css` does not exist and will break the build if imported.
271+
CHECK Every color, spacing, and font value comes from @contentful/f36-tokens — no hardcoded hex, px, or font stacks (exception: shell structure dimensions like sidebar width, border-radius, and nav item padding that have no token equivalent — see base-shell.md)
273272
CHECK Every icon name exists in @contentful/f36-icons — see guidelines/foundations/icons.md. If unsure, DON'T guess.
274273
CHECK Every input is inside a FormControl with a Label
275274
CHECK Only ONE primary button per visible screen section
276275
CHECK All labels and headings use sentence case — never Title Case or ALL CAPS
277276
CHECK Destructive confirmation cancel label is "Never mind" — not "Cancel"
278277
CHECK No usage of Workbench — use Layout instead
279-
CHECK No fabricated sub-components: Layout only has Layout.Header, Layout.Body, Layout.Sidebar. Skeleton only has Skeleton.Container, Skeleton.DisplayText, Skeleton.BodyText, Skeleton.Image.
278+
CHECK No fabricated sub-components: Layout only has Layout.Header, Layout.Body, Layout.Sidebar. Skeleton only has Skeleton.Container, Skeleton.DisplayText, Skeleton.BodyText, Skeleton.Image, Skeleton.Row.
279+
CHECK Every Button and IconButton uses size="small" — never omit (defaults to medium/40px).
280280
```

skills/examples/app-shell.tsx

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import {
2+
Box,
3+
Flex,
4+
Header,
5+
Button,
6+
Table,
7+
TextInput,
8+
EntityStatusBadge,
9+
Menu,
10+
IconButton,
11+
Pagination,
12+
Text,
13+
} from '@contentful/f36-components';
14+
// Navbar is a separate package — not in f36-components
15+
import { Navbar } from '@contentful/f36-navbar';
16+
import {
17+
PlusIcon,
18+
MagnifyingGlassIcon,
19+
DotsThreeIcon,
20+
PenNibIcon,
21+
ImageSquareIcon,
22+
SparkleIcon,
23+
GlobeIcon,
24+
GearSixIcon,
25+
TreeStructureIcon,
26+
TagIcon,
27+
} from '@contentful/f36-icons';
28+
import tokens from '@contentful/f36-tokens';
29+
30+
// Full app shell: Navbar (60px) + sidebar (280px) + content area
31+
// Only include structural elements the design shows — see base-shell.md
32+
function AppShellExample() {
33+
return (
34+
<Box
35+
style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}
36+
>
37+
{/* Navbar from @contentful/f36-navbar — never hand-build with Flex/Box */}
38+
{/* Requires global box-sizing: border-box or it renders 92px instead of 60px */}
39+
<Navbar
40+
mainNavigation={
41+
<>
42+
<Navbar.Item title="Content model" icon={<PenNibIcon />} />
43+
<Navbar.Item title="Content" icon={<ImageSquareIcon />} isActive />
44+
<Navbar.Item title="Experiences" icon={<SparkleIcon />} />
45+
<Navbar.Item title="Media" icon={<GlobeIcon />} />
46+
<Navbar.Item title="Apps">
47+
<Navbar.MenuItem title="Installed apps" />
48+
<Navbar.MenuItem title="Marketplace" />
49+
</Navbar.Item>
50+
</>
51+
}
52+
switcher={
53+
<Navbar.Switcher
54+
space="Acme Corp"
55+
environment="Production"
56+
envVariant="master"
57+
/>
58+
}
59+
account={
60+
<Navbar.Account
61+
username="Conny Contentful"
62+
initials="CC"
63+
label="Account"
64+
>
65+
<Navbar.MenuItem title="Account settings" />
66+
<Navbar.MenuDivider />
67+
<Navbar.MenuItem title="Log out" />
68+
</Navbar.Account>
69+
}
70+
/>
71+
72+
<Flex style={{ flex: 1 }}>
73+
{/* Sidebar: 280px default, flexShrink: 0, top-left border-radius 12px */}
74+
<Box
75+
style={{
76+
width: '280px',
77+
flexShrink: 0,
78+
backgroundColor: tokens.colorWhite,
79+
borderRadius: '12px 0 0 0',
80+
padding: '24px 12px 8px',
81+
}}
82+
>
83+
{/* Section title: 12px, weight 600, gray700 */}
84+
<Text
85+
style={{
86+
fontSize: tokens.fontSizeS,
87+
fontWeight: tokens.fontWeightDemiBold,
88+
color: tokens.gray700,
89+
display: 'block',
90+
height: '28px',
91+
paddingLeft: tokens.spacingM,
92+
}}
93+
>
94+
Content
95+
</Text>
96+
97+
<Flex flexDirection="column" style={{ gap: tokens.spacing2Xs }}>
98+
{/* Active nav item: blue100 background, 34px tall, 4px radius */}
99+
<Flex
100+
alignItems="center"
101+
style={{
102+
height: '34px',
103+
padding: `${tokens.spacing2Xs} ${tokens.spacingS}`,
104+
gap: tokens.spacingXs,
105+
borderRadius: tokens.borderRadiusSmall,
106+
backgroundColor: tokens.blue100,
107+
cursor: 'pointer',
108+
}}
109+
>
110+
<ImageSquareIcon size="small" />
111+
<Text style={{ color: tokens.gray900 }}>Entries</Text>
112+
</Flex>
113+
114+
<Flex
115+
alignItems="center"
116+
style={{
117+
height: '34px',
118+
padding: `${tokens.spacing2Xs} ${tokens.spacingS}`,
119+
gap: tokens.spacingXs,
120+
borderRadius: tokens.borderRadiusSmall,
121+
cursor: 'pointer',
122+
}}
123+
>
124+
<TreeStructureIcon size="small" />
125+
<Text style={{ color: tokens.gray900 }}>Content types</Text>
126+
</Flex>
127+
128+
<Flex
129+
alignItems="center"
130+
style={{
131+
height: '34px',
132+
padding: `${tokens.spacing2Xs} ${tokens.spacingS}`,
133+
gap: tokens.spacingXs,
134+
borderRadius: tokens.borderRadiusSmall,
135+
cursor: 'pointer',
136+
}}
137+
>
138+
<TagIcon size="small" />
139+
<Text style={{ color: tokens.gray900 }}>Tags</Text>
140+
</Flex>
141+
</Flex>
142+
</Box>
143+
144+
{/* Content area: flex 1, 12px top padding, 24px horizontal */}
145+
<Box
146+
style={{
147+
flex: 1,
148+
backgroundColor: tokens.colorWhite,
149+
borderRadius: '12px 12px 0 0',
150+
padding: `${tokens.spacingS} ${tokens.spacingL} 0`,
151+
}}
152+
>
153+
{/* Header with title + primary action */}
154+
<Header
155+
title="Entries"
156+
actions={
157+
<Button variant="primary" size="small" startIcon={<PlusIcon />}>
158+
Add entry
159+
</Button>
160+
}
161+
filters={
162+
<TextInput
163+
type="search"
164+
placeholder="Search entries"
165+
icon={<MagnifyingGlassIcon />}
166+
/>
167+
}
168+
/>
169+
170+
{/* Table: full width, no horizontal padding, 36px header rows, 56px body rows */}
171+
<Table>
172+
<Table.Head>
173+
<Table.Row>
174+
<Table.Cell>Name</Table.Cell>
175+
<Table.Cell>Content type</Table.Cell>
176+
<Table.Cell>Updated</Table.Cell>
177+
<Table.Cell>Status</Table.Cell>
178+
<Table.Cell width="60px" />
179+
</Table.Row>
180+
</Table.Head>
181+
<Table.Body>
182+
<Table.Row>
183+
<Table.Cell>Homepage banner</Table.Cell>
184+
<Table.Cell>Banner</Table.Cell>
185+
<Table.Cell>2 hours ago</Table.Cell>
186+
<Table.Cell>
187+
<EntityStatusBadge entityStatus="published" />
188+
</Table.Cell>
189+
<Table.Cell>
190+
<Menu>
191+
<Menu.Trigger>
192+
<IconButton
193+
icon={<DotsThreeIcon />}
194+
aria-label="Actions"
195+
variant="transparent"
196+
size="small"
197+
/>
198+
</Menu.Trigger>
199+
<Menu.List>
200+
<Menu.Item>Edit</Menu.Item>
201+
<Menu.Item>Duplicate</Menu.Item>
202+
<Menu.Divider />
203+
<Menu.Item>Delete</Menu.Item>
204+
</Menu.List>
205+
</Menu>
206+
</Table.Cell>
207+
</Table.Row>
208+
</Table.Body>
209+
</Table>
210+
211+
<Flex justifyContent="center" marginTop="spacingL">
212+
<Pagination
213+
activePage={1}
214+
onPageChange={() => {}}
215+
totalItems={50}
216+
itemsPerPage={20}
217+
/>
218+
</Flex>
219+
</Box>
220+
</Flex>
221+
</Box>
222+
);
223+
}
224+
225+
export { AppShellExample };

skills/examples/confirmation-modal.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// REQUIRED: CSS import — without this, all components render unstyled
2-
import '@contentful/f36-components/dist/styles.css';
31
import { Modal, Button, Paragraph } from '@contentful/f36-components';
42
import { useState } from 'react';
53

@@ -16,7 +14,7 @@ function DeleteContentTypeModal() {
1614

1715
return (
1816
<>
19-
<Button variant="negative" onClick={() => setIsOpen(true)}>
17+
<Button variant="negative" size="small" onClick={() => setIsOpen(true)}>
2018
Delete content type
2119
</Button>
2220

@@ -38,11 +36,16 @@ function DeleteContentTypeModal() {
3836
</Modal.Content>
3937
<Modal.Controls>
4038
{/* Cancel label MUST be "Never mind" for destructive confirmations — never "Cancel" */}
41-
<Button variant="secondary" onClick={() => setIsOpen(false)}>
39+
<Button
40+
variant="secondary"
41+
size="small"
42+
onClick={() => setIsOpen(false)}
43+
>
4244
Never mind
4345
</Button>
4446
<Button
4547
variant="negative"
48+
size="small"
4649
isLoading={isDeleting}
4750
onClick={handleDelete}
4851
>

skills/examples/detail-page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// REQUIRED: CSS import — without this, all components render unstyled
2-
import '@contentful/f36-components/dist/styles.css';
31
import {
42
Layout,
53
Header,
@@ -28,8 +26,12 @@ function EntryDetailPage() {
2826
dividerLine
2927
actions={
3028
<Stack spacing="spacingS">
31-
<Button variant="secondary">Save</Button>
32-
<Button variant="positive">Publish</Button>
29+
<Button variant="secondary" size="small">
30+
Save
31+
</Button>
32+
<Button variant="positive" size="small">
33+
Publish
34+
</Button>
3335
</Stack>
3436
}
3537
/>

0 commit comments

Comments
 (0)