@canonical/code-standards 0.1.0

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/data/code.ttl ADDED
@@ -0,0 +1,437 @@
1
+ @prefix cs: <http://pragma.canonical.com/codestandards#> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
+
7
+ # General Code Category
8
+ cs:CodeCategory a cs:Category ;
9
+ rdfs:label "Code"@en ;
10
+ rdfs:comment "General typescript coding standards and best practices"@en ;
11
+ cs:slug "code" .
12
+
13
+ # Function Purity Standard
14
+ cs:FunctionPurity a cs:CodeStandard ;
15
+ cs:name "code/function/purity" ;
16
+ cs:hasCategory cs:CodeCategory ;
17
+ cs:description "Functions should be pure where possible. A pure function returns the same output for the same input and does not modify external state or perform I/O operations. If a function must perform side effects (e.g., I/O in CLI tools), it must be explicitly annotated and documented with the reason for impurity using the @note tag in TSDoc/JSDoc." ;
18
+ cs:dos """
19
+ (Do) Create pure functions that rely only on their inputs to compute the output.
20
+ ```typescript
21
+ const calculatePrice = (basePrice: number, taxRate: number): number => {
22
+ return basePrice * (1 + taxRate);
23
+ };
24
+ ```
25
+
26
+ (Do) Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note.
27
+ ```typescript
28
+ /**
29
+ * Writes data to the file system.
30
+ * @note - This function is impure - it modifies the file system.
31
+ */
32
+ function writeOutputToFile(data: string, path: string) {
33
+ fs.writeFileSync(path, data);
34
+ }
35
+ ```
36
+ """ ;
37
+ cs:donts """
38
+ (Don't) Create impure functions without annotation or documentation.
39
+ ```typescript
40
+ let total = 0;
41
+ const addToTotal = (value: number) => {
42
+ total += value; // Modifies external state, not documented
43
+ };
44
+ ```
45
+
46
+ (Don't) Hide side effects in functions that appear pure.
47
+ ```typescript
48
+ function getConfig() {
49
+ // Reads from disk without annotation
50
+ return fs.readFileSync('config.json');
51
+ }
52
+ ```
53
+ """ .
54
+
55
+ # Function Composition Standard
56
+ cs:FunctionComposition a cs:CodeStandard ;
57
+ cs:name "code/function/composition" ;
58
+ cs:hasCategory cs:CodeCategory ;
59
+ cs:description "Each function must handle exactly one specific task. Complex operations must be broken down into smaller, single-purpose functions." ;
60
+ cs:dos """
61
+ (Do) Define functions with a single, clear responsibility.
62
+ ```typescript
63
+ const validateEmail = (email: string): boolean => {
64
+ return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
65
+ };
66
+ ```
67
+
68
+ (Do) Compose single-purpose functions to build complex logic.
69
+ ```typescript
70
+ const validatePassword = (password: string): boolean => {
71
+ return password.length >= 8;
72
+ };
73
+
74
+ const validateForm = (email: string, password:string): boolean => {
75
+ return validateEmail(email) && validatePassword(password);
76
+ };
77
+ ```
78
+ """ ;
79
+ cs:donts """
80
+ (Don't) Mix multiple responsibilities in a single function.
81
+ ```typescript
82
+ const processUserData = (user: any) => {
83
+ // Validates user data
84
+ // Transforms the data
85
+ // Saves to the database
86
+ // Sends a notification
87
+ };
88
+ ```
89
+ """ .
90
+
91
+ # Function Location Standard
92
+ cs:FunctionLocation a cs:CodeStandard ;
93
+ cs:name "code/function/location" ;
94
+ cs:hasCategory cs:CodeCategory ;
95
+ cs:description "Functions must be defined as close to their broadest point of use as possible. Shared utilities must be placed in a common location, and each function must serve a single feature or responsibility." ;
96
+ cs:dos """
97
+ (Do) Place functions in the file or module where they are most broadly used. If a function is shared across multiple components or modules, place it in a common utility location.
98
+ ```typescript
99
+ // utils/math.ts
100
+ export function calculateSum(a: number, b: number): number {
101
+ return a + b;
102
+ }
103
+
104
+ // Used in multiple places
105
+ import { calculateSum } from './utils/math.js';
106
+
107
+ // For single-use functions, keep them close to their usage:
108
+ ```typescript
109
+ // services/UserService.ts
110
+ const formatUserName = (user: User) => `${user.firstName} ${user.lastName}`;
111
+
112
+ const getProfileData = (user: User) => {
113
+ const formattedName = formatUserName(user);
114
+ return {
115
+ ...user,
116
+ formattedName,
117
+ };
118
+ };
119
+
120
+ """ ;
121
+ cs:donts """
122
+ (Don't) Place functions far from where they are used, or in a generic location if only used in one place.
123
+ ```typescript
124
+ // utils/validators.ts
125
+ export const validateUser = (user: User) => { ... }; // Only used in one component
126
+
127
+ // services/UserService.ts
128
+ import { validateUser } from '../utils/validators.js'; // Less ideal if only used here
129
+
130
+ // Don't mix unrelated functions in a single file:
131
+ ```typescript
132
+ export const mathFunc = () => {};
133
+ export const stringFunc = () => {};
134
+ """ .
135
+
136
+ # API Stability Standard
137
+ cs:APIStability a cs:CodeStandard ;
138
+ cs:name "code/api/stability" ;
139
+ cs:hasCategory cs:CodeCategory ;
140
+ cs:description "Experimental APIs must be marked with the @experimental JSDoc tag in type definition files. The tag must include a description of what is experimental." ;
141
+ cs:dos """
142
+ (Do) Mark an experimental interface with a clear @experimental JSDoc tag and description.
143
+ ```typescript
144
+ /**
145
+ * Configuration for the data processing pipeline.
146
+ * @experimental The streaming API is experimental and may change
147
+ * in future releases. Currently, it only supports JSON data.
148
+ */
149
+ interface PipelineConfig {
150
+ // ...
151
+ }
152
+ ```
153
+
154
+ (Do) Add an @experimental tag to a specific property with context about its experimental status.
155
+ ```typescript
156
+ interface PipelineConfig {
157
+ /**
158
+ * @experimental The custom transformers API is in beta, and the interface
159
+ * may change to support stronger type validation.
160
+ */
161
+ transformers?: DataTransformer[];
162
+ }
163
+ ```
164
+
165
+ (Do) Describe the experimental status and any future plans for the API.
166
+ ```typescript
167
+ interface CacheConfig {
168
+ /**
169
+ * @experimental The distributed cache API is experimental and will be
170
+ * replaced with a new consensus-based implementation in v2.1.
171
+ */
172
+ distributed?: boolean;
173
+ }
174
+ ```
175
+ """ ;
176
+ cs:donts """
177
+ (Don't) Use the @experimental tag without an explanation.
178
+ ```typescript
179
+ interface ProcessorConfig {
180
+ /** @experimental */ // Bad: No context provided.
181
+ streaming?: boolean;
182
+ }
183
+ ```
184
+
185
+ (Don't) Use the @experimental tag without describing what is experimental.
186
+ ```typescript
187
+ /**
188
+ * @experimental // Bad: No description of what is experimental.
189
+ */
190
+ interface QueueConfig {
191
+ processor: (item: any) => Promise<void>;
192
+ }
193
+ ```
194
+ """ .
195
+
196
+ # Function Verb Naming Standard
197
+ cs:FunctionVerbNaming a cs:CodeStandard ;
198
+ cs:name "code/naming/function-verb" ;
199
+ cs:hasCategory cs:CodeCategory ;
200
+ cs:description "All function and method names must start with a verb that describes the action the function performs. Names like `pathToX` or `dataForUser` describe a result, not an action — use `findPathToX` or `fetchDataForUser` instead. This makes the function's intent immediately clear and distinguishes functions from plain values or constants." ;
201
+ cs:dos """
202
+ (Do) Start function names with an action verb:
203
+ ```typescript
204
+ const findPathToModule = (name: string): string => { /* ... */ };
205
+ const fetchDataForUser = (userId: string): Promise<User> => { /* ... */ };
206
+ const parseConfigFile = (path: string): Config => { /* ... */ };
207
+ const calculateTotalPrice = (items: Item[]): number => { /* ... */ };
208
+ const isValidEmail = (email: string): boolean => { /* ... */ };
209
+ const hasPermission = (user: User, action: string): boolean => { /* ... */ };
210
+ ```
211
+
212
+ (Do) Use descriptive verb prefixes that convey the operation:
213
+ ```typescript
214
+ // Retrieval: get, fetch, find, resolve, load, read
215
+ const getUser = (id: string): User => { /* ... */ };
216
+ const fetchOrders = (): Promise<Order[]> => { /* ... */ };
217
+ const findMatchingRule = (rules: Rule[], input: string): Rule | undefined => { /* ... */ };
218
+
219
+ // Transformation: format, parse, convert, transform, map, normalize
220
+ const formatDate = (date: Date): string => { /* ... */ };
221
+ const parseResponse = (raw: string): Response => { /* ... */ };
222
+
223
+ // Validation: is, has, can, should, validate, check
224
+ const isActive = (user: User): boolean => { /* ... */ };
225
+ const hasExpired = (token: Token): boolean => { /* ... */ };
226
+ const validateInput = (data: unknown): data is FormData => { /* ... */ };
227
+
228
+ // Mutation: set, update, add, remove, delete, reset, clear
229
+ const setTheme = (theme: Theme): void => { /* ... */ };
230
+ const removeItem = (id: string): void => { /* ... */ };
231
+
232
+ // Creation: create, build, generate, compose, make
233
+ const createConnection = (config: Config): Connection => { /* ... */ };
234
+ const buildQuery = (params: Params): string => { /* ... */ };
235
+ ```
236
+ """ ;
237
+ cs:donts """
238
+ (Don't) Name functions as nouns or noun phrases — they read like values, not actions:
239
+ ```typescript
240
+ // Bad: reads like a variable, not a function
241
+ const pathToModule = (name: string): string => { /* ... */ };
242
+ // Good: findPathToModule
243
+
244
+ const dataForUser = (userId: string): Promise<User> => { /* ... */ };
245
+ // Good: fetchDataForUser
246
+
247
+ const userPermissions = (user: User): Permission[] => { /* ... */ };
248
+ // Good: getPermissions or listPermissions
249
+ ```
250
+
251
+ (Don't) Use vague or non-descriptive verbs that don't convey meaning:
252
+ ```typescript
253
+ // Bad: "do" and "process" are too vague on their own
254
+ const doEmail = (email: string) => { /* ... */ };
255
+ // Good: sendEmail, validateEmail, formatEmail
256
+
257
+ const processUser = (user: User) => { /* ... */ };
258
+ // Good: activateUser, deactivateUser, updateUser
259
+ ```
260
+ """ .
261
+
262
+ # Constants File Standard
263
+ cs:ConstantsFile a cs:CodeStandard ;
264
+ cs:name "code/constants/file" ;
265
+ cs:hasCategory cs:CodeCategory ;
266
+ cs:description "Domain constants must be collected in a `constants.ts` file using named exports (no default export). The file must be colocated at the level of the domain that owns the constants — inside the domain folder when one exists. Constants files always use named exports so consumers can import exactly what they need. Single-use constants that are only referenced in one file may be declared inline in that file instead of being extracted to `constants.ts`." ;
267
+ cs:dos """
268
+ (Do) Create a `constants.ts` file with named exports at the domain level:
269
+ ```typescript
270
+ // features/pricing/constants.ts
271
+ export const DEFAULT_CURRENCY = "USD";
272
+ export const TAX_RATE = 0.21;
273
+ export const MAX_DISCOUNT_PERCENT = 50;
274
+ ```
275
+
276
+ (Do) Colocate `constants.ts` inside the domain folder it belongs to:
277
+ ```
278
+ features/
279
+ ├── pricing/
280
+ │ ├── constants.ts # Domain constants live here
281
+ │ ├── calculatePrice.ts
282
+ │ └── index.ts
283
+ ├── auth/
284
+ │ ├── constants.ts # Auth-specific constants
285
+ │ ├── validateToken.ts
286
+ │ └── index.ts
287
+ ```
288
+
289
+ (Do) Import constants directly from the owning domain's `constants.ts`:
290
+ ```typescript
291
+ // features/pricing/calculatePrice.ts
292
+ import { TAX_RATE, DEFAULT_CURRENCY } from "./constants.js";
293
+ ```
294
+
295
+ (Do) Keep all exports in `constants.ts` as named exports with the same shape (plain values):
296
+ ```typescript
297
+ // constants.ts
298
+ export const RECONNECT_INTERVAL_MS = 5000;
299
+ export const MAX_RETRIES = 3;
300
+ export const DEFAULT_TIMEOUT_MS = 30_000;
301
+ ```
302
+
303
+ (Do) Keep single-use constants inline in the file that uses them:
304
+ ```typescript
305
+ // features/pricing/applyDiscount.ts
306
+ const MAX_DISCOUNT_PERCENT = 50;
307
+
308
+ export default function applyDiscount(price: number, rate: number): number {
309
+ const capped = Math.min(rate, MAX_DISCOUNT_PERCENT / 100);
310
+ return price * (1 - capped);
311
+ }
312
+ ```
313
+ """ ;
314
+ cs:donts """
315
+ (Don't) Use a default export in a constants file:
316
+ ```typescript
317
+ // Bad: default export forces consumers to name the import
318
+ export default {
319
+ DEFAULT_CURRENCY: "USD",
320
+ TAX_RATE: 0.21,
321
+ };
322
+ ```
323
+
324
+ (Don't) Scatter constants across unrelated files instead of collecting them in `constants.ts`:
325
+ ```typescript
326
+ // Bad: constants buried inside implementation files
327
+ // calculatePrice.ts
328
+ export const TAX_RATE = 0.21;
329
+ const calculatePrice = (base: number) => base * (1 + TAX_RATE);
330
+ export default calculatePrice;
331
+ ```
332
+
333
+ (Don't) Place constants far from the domain that owns them:
334
+ ```typescript
335
+ // Bad: pricing constants in a top-level shared file
336
+ // src/constants.ts
337
+ export const TAX_RATE = 0.21; // Belongs in pricing/
338
+ export const MAX_RETRIES = 3; // Belongs in network/
339
+ export const SESSION_TTL_MS = 3600; // Belongs in auth/
340
+ ```
341
+
342
+ (Don't) Mix constants with functions, types, or other non-constant exports:
343
+ ```typescript
344
+ // Bad: constants.ts should only contain constant values
345
+ export const MAX_RETRIES = 3;
346
+ export const formatRetryMessage = (n: number) => `Retry ${n} of ${MAX_RETRIES}`;
347
+ export type RetryConfig = { max: number };
348
+ ```
349
+ """ .
350
+
351
+ # Types File Standard
352
+ cs:TypesFile a cs:CodeStandard ;
353
+ cs:name "code/types/file" ;
354
+ cs:hasCategory cs:CodeCategory ;
355
+ cs:description "Reusable domain types must be collected in a `types.ts` file using named exports (no default export), colocated at the level of the domain that owns them. Types that are only used in a single file may be declared inline in that file instead of being extracted to `types.ts`." ;
356
+ cs:dos """
357
+ (Do) Create a `types.ts` file with named exports for types shared across the domain:
358
+ ```typescript
359
+ // features/pricing/types.ts
360
+ export type Currency = "USD" | "EUR" | "GBP";
361
+ export interface PriceBreakdown {
362
+ base: number;
363
+ tax: number;
364
+ total: number;
365
+ }
366
+ export type DiscountStrategy = "percentage" | "fixed";
367
+ ```
368
+
369
+ (Do) Colocate `types.ts` inside the domain folder it belongs to:
370
+ ```
371
+ features/
372
+ ├── pricing/
373
+ │ ├── types.ts # Shared domain types live here
374
+ │ ├── constants.ts
375
+ │ ├── calculatePrice.ts
376
+ │ └── index.ts
377
+ ├── auth/
378
+ │ ├── types.ts # Auth-specific types
379
+ │ ├── validateToken.ts
380
+ │ └── index.ts
381
+ ```
382
+
383
+ (Do) Keep single-use types inline in the file that uses them:
384
+ ```typescript
385
+ // features/pricing/applyDiscount.ts
386
+ type DiscountResult = { discounted: number; savings: number };
387
+
388
+ export default function applyDiscount(price: number, rate: number): DiscountResult {
389
+ const savings = price * rate;
390
+ return { discounted: price - savings, savings };
391
+ }
392
+ ```
393
+
394
+ (Do) Import shared types from the owning domain's `types.ts`:
395
+ ```typescript
396
+ // features/pricing/calculatePrice.ts
397
+ import type { Currency, PriceBreakdown } from "./types.js";
398
+ ```
399
+ """ ;
400
+ cs:donts """
401
+ (Don't) Use a default export in a types file:
402
+ ```typescript
403
+ // Bad: default export for a collection of types
404
+ export default interface PriceBreakdown {
405
+ base: number;
406
+ tax: number;
407
+ total: number;
408
+ }
409
+ ```
410
+
411
+ (Don't) Extract single-use types to `types.ts` when they are only used in one file:
412
+ ```typescript
413
+ // Bad: types.ts contains a type only used in applyDiscount.ts
414
+ // types.ts
415
+ export type DiscountResult = { discounted: number; savings: number };
416
+
417
+ // applyDiscount.ts
418
+ import type { DiscountResult } from "./types.js"; // Unnecessary indirection
419
+ ```
420
+
421
+ (Don't) Place types far from the domain that owns them:
422
+ ```typescript
423
+ // Bad: all types dumped in a top-level shared file
424
+ // src/types.ts
425
+ export type Currency = "USD" | "EUR" | "GBP"; // Belongs in pricing/
426
+ export type AuthToken = { jwt: string }; // Belongs in auth/
427
+ export type RetryPolicy = { max: number }; // Belongs in network/
428
+ ```
429
+
430
+ (Don't) Mix types with functions, constants, or other non-type exports:
431
+ ```typescript
432
+ // Bad: types.ts should only contain type declarations
433
+ export type Currency = "USD" | "EUR" | "GBP";
434
+ export const DEFAULT_CURRENCY: Currency = "USD";
435
+ export const formatCurrency = (value: number, currency: Currency) => `${currency} ${value}`;
436
+ ```
437
+ """ .
package/data/css.ttl ADDED
@@ -0,0 +1,265 @@
1
+ @prefix cs: <http://pragma.canonical.com/codestandards#> .
2
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
3
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
+
7
+ # CSS Category
8
+ cs:CSSCategory a cs:Category ;
9
+ rdfs:label "CSS"@en ;
10
+ rdfs:comment "Standards for CSS technical implementation"@en ;
11
+ cs:slug "css" .
12
+
13
+ # CSS Selector Namespace Standard
14
+ cs:CSSSelectorNamespace a cs:CodeStandard ;
15
+ cs:name "css/selectors/namespace" ;
16
+ cs:hasCategory cs:CSSCategory ;
17
+ cs:description "All component selectors must be prefixed with the `.ds` namespace (e.g., `.ds.button`)." ;
18
+ cs:dos """
19
+ (Do) Prefix all component selectors with `.ds`.
20
+ ```css
21
+ /* Component root with namespace */
22
+ .ds.button {
23
+ /* Base styles */
24
+ }
25
+ ```
26
+ """ ;
27
+ cs:donts """
28
+ (Don't) Omit the `.ds` namespace from component selectors.
29
+ ```css
30
+ /* Bad: Missing .ds namespace */
31
+ .button {
32
+ /* styles */
33
+ }
34
+ ```
35
+ """ .
36
+
37
+ # CSS Component Encapsulation Standard
38
+ cs:CSSComponentEncapsulation a cs:CodeStandard ;
39
+ cs:name "css/component/encapsulation" ;
40
+ cs:hasCategory cs:CSSCategory ;
41
+ cs:description "Component styles must be encapsulated using the component's root class as a boundary. All internal element styles must be scoped to the component's namespace." ;
42
+ cs:dos """
43
+ (Do) Scope internal element styles using the component's namespace.
44
+ ```css
45
+ .ds.button {
46
+ /* Component root styles */
47
+
48
+ & > .icon {
49
+ /* Internal element styles */
50
+ margin-right: var(--button-icon-spacing);
51
+ }
52
+ }
53
+ ```
54
+ """ ;
55
+ cs:donts """
56
+ (Don't) Don't style internal elements without the component namespace.
57
+ ```css
58
+ /* Bad: Internal element not scoped to component */
59
+ .icon {
60
+ margin-right: var(--button-icon-spacing);
61
+ }
62
+ ```
63
+ """ .
64
+
65
+ # CSS State Handling Standard
66
+ cs:CSSStateHandling a cs:CodeStandard ;
67
+ cs:name "css/component/states" ;
68
+ cs:hasCategory cs:CSSCategory ;
69
+ cs:description "Component states must be handled using attribute selectors for native states and class modifiers for custom states." ;
70
+ cs:dos """
71
+ (Do) Use attribute selectors for native element states.
72
+ ```css
73
+ .ds.button {
74
+ &[disabled] {
75
+ opacity: var(--button-disabled-opacity);
76
+ }
77
+ }
78
+ ```
79
+ """ ;
80
+ cs:donts """
81
+ (Don't) Use class modifiers for native states.
82
+ ```css
83
+ /* Bad: Using class for native state */
84
+ .ds.button.disabled {
85
+ opacity: var(--button-disabled-opacity);
86
+ }
87
+ ```
88
+ """ .
89
+
90
+ # CSS Specificity Management Standard
91
+ cs:CSSSpecificityManagement a cs:CodeStandard ;
92
+ cs:name "css/selectors/specificity" ;
93
+ cs:hasCategory cs:CSSCategory ;
94
+ cs:description """CSS selectors must follow a strict specificity pattern:
95
+ - Component root must use namespace + component name (.ds.button)
96
+ - Single modifier class for variants (.ds.button.primary)
97
+ - Single attribute for states (.ds.button[disabled])""" ;
98
+ cs:dos """
99
+ (Do) Use a single modifier class for component variants.
100
+ ```css
101
+ .ds.button.primary {
102
+ /* Variant: root + modifier (3 classes) */
103
+ background: var(--button-primary-background);
104
+ }
105
+ ```
106
+ """ ;
107
+ cs:donts """
108
+ (Don't) Combine multiple modifiers or mix states with variants.
109
+ ```css
110
+ /* Bad: Mixing variant with state */
111
+ .ds.button.primary[disabled].large {
112
+ /* Too specific: root + 2 modifiers + state */
113
+ }
114
+ ```
115
+ """ .
116
+
117
+ # CSS Selector Semantics Standard
118
+ cs:CSSSelectorSemantics a cs:CodeStandard ;
119
+ cs:name "css/selectors/semantics" ;
120
+ cs:hasCategory cs:CSSCategory ;
121
+ cs:description "CSS class names must describe the purpose or state of an element, not its appearance." ;
122
+ cs:dos """
123
+ (Do) Use semantic modifier classes to represent component variations.
124
+ ```css
125
+ /* Semantic modifier for a primary button */
126
+ .ds.button.primary {
127
+ --modifier-color: var(--color-primary);
128
+ }
129
+ ```
130
+ """ ;
131
+ cs:donts """
132
+ (Don't) Use non-semantic or presentational class names.
133
+ ```css
134
+ /* Bad: 'big' describes appearance, not purpose */
135
+ .ds.button.big {
136
+ padding: 1rem;
137
+ }
138
+ ```
139
+ """ .
140
+
141
+ # Theme Activation Standard
142
+ cs:ThemeActivation a cs:CodeStandard ;
143
+ cs:name "css/themes/activation" ;
144
+ cs:hasCategory cs:CSSCategory ;
145
+ cs:description """Theme tokens must be activated through CSS classes on container elements. See styling/themes/definition for theme token structure.""" ;
146
+ cs:dos """
147
+ (Do) Define semantic tokens within theme classes.
148
+ ```css
149
+ .canonical {
150
+ --spacing-vertical-medium: var(--spacing-unit-2x);
151
+ --color-background: var(--color-neutral-100);
152
+ }
153
+ ```
154
+ """ ;
155
+ cs:donts """
156
+ (Don't) Hardcode theme names in component styles.
157
+ ```css
158
+ /* Bad: Component locked to specific theme */
159
+ .ds.button {
160
+ padding: var(--canonical-spacing-vertical-medium);
161
+ }
162
+ ```
163
+ """ .
164
+
165
+ # CSS Property Values Standard
166
+ cs:CSSPropertyValues a cs:CodeStandard ;
167
+ cs:name "css/properties/values" ;
168
+ cs:hasCategory cs:CSSCategory ;
169
+ cs:description """CSS properties must use design tokens for design decisions (see styling/tokens/creation) and raw values for properties that are independent from design decisions, or "unthemable".""" ;
170
+ cs:dos """
171
+ (Do) Use design tokens for design decisions.
172
+ ```css
173
+ .ds.button {
174
+ /* Design decision uses token */
175
+ background: var(--button-background);
176
+ }
177
+ ```
178
+ (Do) Use raw values for unthemable properties (independent of design decisions).
179
+ ```css
180
+ .ds.skip-link {
181
+ visibility: hidden;
182
+ }
183
+ ```
184
+ """ ;
185
+ cs:donts """
186
+ (Don't) Use raw values for design decisions.
187
+ ```css
188
+ .ds.button {
189
+ /* Bad: Design decision using raw value */
190
+ background: #0066CC;
191
+ }
192
+ ```
193
+ """ .
194
+
195
+ # Child Element Naming Standard
196
+ cs:CSSChildElementNaming a cs:CodeStandard ;
197
+ cs:name "css/selectors/child-elements" ;
198
+ cs:hasCategory cs:CSSCategory ;
199
+ cs:description """Child elements within components must use simple, role-based class names (e.g., `.header`, `.content`, `.icon`) scoped by the parent selector, NOT verbose prefixed names that repeat the component name.""" ;
200
+ cs:dos """
201
+ (Do) Use simple role-based class names scoped by the parent.
202
+ ```css
203
+ .ds.accordion-item {
204
+ & > .header {
205
+ /* Header styles */
206
+ }
207
+
208
+ & > .header > .chevron {
209
+ /* Chevron indicator */
210
+ }
211
+
212
+ & > .header > .heading {
213
+ /* Heading text */
214
+ }
215
+
216
+ & > .content {
217
+ /* Collapsible content */
218
+ }
219
+ }
220
+
221
+ .ds.breadcrumbs-item {
222
+ & > .link {
223
+ /* Link styles */
224
+ }
225
+
226
+ & > .separator {
227
+ /* Separator styles */
228
+ }
229
+ }
230
+ ```
231
+ """ ;
232
+ cs:donts """
233
+ (Don't) Use verbose prefixed class names that repeat the component name.
234
+ ```css
235
+ /* Bad: Redundant component prefix in child class names */
236
+ .ds.accordion-item .accordion-item-header {
237
+ /* 'accordion-item-' prefix is redundant */
238
+ }
239
+
240
+ .ds.accordion-item .accordion-item-chevron {
241
+ /* Already scoped by parent, no need to repeat */
242
+ }
243
+
244
+ .ds.timeline-event .timeline-event-marker {
245
+ /* Verbose and BEM-like */
246
+ }
247
+ ```
248
+ """ .
249
+
250
+ # Component Naming Convention for CSS
251
+ cs:CSSComponentNamingConvention a cs:CodeStandard ;
252
+ cs:name "css/selectors/naming-convention" ;
253
+ cs:hasCategory cs:CSSCategory ;
254
+ cs:description "Component CSS class names must be the kebab-case version of the component's PascalCase name." ;
255
+ cs:dos """
256
+ (Do) Convert PascalCase component names to kebab-case for CSS classes:
257
+ - `MyComponent` -> `.ds.my-component`
258
+ - `UserProfile` -> `.ds.user-profile`
259
+ - `Button` -> `.ds.button`
260
+ """ ;
261
+ cs:donts """
262
+ (Don't) Use PascalCase or other formats in CSS class names:
263
+ - `.ds.MyComponent` (Bad: Not kebab-case)
264
+ - `.ds.user_profile` (Bad: Not kebab-case)
265
+ """ .