@markuplint/html-spec 4.16.0 → 4.17.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.
@@ -0,0 +1,521 @@
1
+ # Element Specification Format
2
+
3
+ This document is a comprehensive reference for the JSON element specification file format
4
+ used by `@markuplint/html-spec`. It covers file naming conventions, the full structure of
5
+ element spec files, content model patterns, attribute definitions, ARIA integration, and
6
+ the shared common files.
7
+
8
+ ## File Naming Conventions
9
+
10
+ ### HTML Elements
11
+
12
+ Pattern: `src/spec.<tag>.json` (e.g., `spec.div.json`, `spec.table.json`, `spec.input.json`)
13
+
14
+ ### SVG Elements
15
+
16
+ Pattern: `src/spec.svg_<local>.json` (e.g., `spec.svg_text.json`, `spec.svg_circle.json`)
17
+
18
+ The local name preserves case (`svg_animateMotion.json`). The element name is inferred
19
+ at runtime as `svg:<local>` (e.g., `svg:text`, `svg:clipPath`).
20
+
21
+ ### Common Files
22
+
23
+ - `spec-common.attributes.json` -- Global attribute category definitions (19 categories)
24
+ - `spec-common.contents.json` -- Content model category macros
25
+
26
+ ### JSON with Comments
27
+
28
+ All spec files support JavaScript-style comments (`//` and `/* */`) via `strip-json-comments`.
29
+ Use comments to link to specification URLs at the top of each file:
30
+
31
+ ```json
32
+ // https://html.spec.whatwg.org/multipage/grouping-content.html#the-p-element
33
+ // https://www.w3.org/TR/html-aria/#el-p
34
+ { ... }
35
+ ```
36
+
37
+ ## Top-Level Structure
38
+
39
+ Each element spec file is a JSON object with four required top-level fields:
40
+
41
+ | Field | Type | Description |
42
+ | -------------- | ------ | -------------------------------------- |
43
+ | `contentModel` | object | Permitted content rules |
44
+ | `globalAttrs` | object | Global attribute category inclusions |
45
+ | `attributes` | object | Element-specific attribute definitions |
46
+ | `aria` | object | ARIA role and property integration |
47
+
48
+ An optional `omission` field may appear in the compiled `index.json` output. It is set
49
+ from MDN data during the build process and is not authored manually in spec files.
50
+
51
+ ## Content Model
52
+
53
+ The `contentModel` object defines what children an element may contain.
54
+
55
+ ### `contentModel.contents`
56
+
57
+ Accepts one of three forms:
58
+
59
+ - `false` -- Void element; no children allowed (e.g., `<input>`, `<br>`)
60
+ - `true` -- Any content allowed (used for certain obsolete elements)
61
+ - An array of content model patterns (see below)
62
+
63
+ ### `contentModel.conditional`
64
+
65
+ An optional array of context-dependent content model overrides. Each entry has a
66
+ `condition` (CSS selector) and `contents` (array of patterns). When matched, the
67
+ conditional content model replaces the default:
68
+
69
+ ```json
70
+ "conditional": [{
71
+ "condition": "dl > div",
72
+ "contents": [{
73
+ "oneOrMore": [
74
+ { "zeroOrMore": ":model(script-supporting)" },
75
+ { "oneOrMore": "dt" },
76
+ { "zeroOrMore": ":model(script-supporting)" },
77
+ { "oneOrMore": "dd" },
78
+ { "zeroOrMore": ":model(script-supporting)" }
79
+ ]
80
+ }]
81
+ }]
82
+ ```
83
+
84
+ ### Cross-Package Relationships
85
+
86
+ The content model definitions in these JSON files are part of a larger ecosystem:
87
+
88
+ - **Schema validation** -- `@markuplint/ml-spec` provides `content-models.schema.json` which defines the valid structure of content model patterns (`require`, `optional`, `oneOrMore`, `zeroOrMore`, `choice`, `transparent`). Source JSON files are validated against this schema in tests.
89
+ - **TypeScript types** -- `@markuplint/ml-spec` auto-generates `ContentModel`, `PermittedContentPattern`, and `Category` types from the schema (in `types/permitted-structures.ts`), enabling type-safe access throughout the codebase.
90
+ - **Runtime algorithms** -- `@markuplint/ml-spec` provides `getContentModel()` which evaluates conditional content models at runtime, and `contentModelCategoryToTagNames()` which resolves category names (e.g., `#flow`) to concrete element lists using `def["#contentModels"]` from this package's `spec-common.contents.json`.
91
+ - **Lint rules** -- `@markuplint/rules` uses these definitions in the `permitted-contents` rule (validates element children against content model patterns) and the `no-empty-palpable-content` rule (uses `#palpable` category data to detect empty palpable elements).
92
+
93
+ ## Content Model Patterns
94
+
95
+ Each pattern uses exactly one key to describe cardinality or composition.
96
+
97
+ ### Cardinality Patterns
98
+
99
+ | Pattern | Meaning |
100
+ | -------------------------------- | ---------------------------- |
101
+ | `{ "require": "<selector>" }` | Exactly one required element |
102
+ | `{ "optional": "<selector>" }` | Zero or one element |
103
+ | `{ "oneOrMore": "<selector>" }` | One or more elements |
104
+ | `{ "zeroOrMore": "<selector>" }` | Zero or more elements |
105
+
106
+ The `oneOrMore` and `zeroOrMore` patterns also accept an array of nested patterns to
107
+ describe a sequence (see the `<div>` conditional example above).
108
+
109
+ ### Composition Patterns
110
+
111
+ | Pattern | Meaning |
112
+ | --------------------------------- | -------------------------------------------------------------- |
113
+ | `{ "choice": [...] }` | One of the given options (each option is an array of patterns) |
114
+ | `{ "transparent": "<selector>" }` | Transparent content model with an exclusion selector |
115
+
116
+ The `choice` pattern contains an array of arrays. The `transparent` pattern means the
117
+ element inherits its parent's content model, excluding elements matching the selector.
118
+
119
+ ### Selector Syntax
120
+
121
+ | Selector | Description |
122
+ | -------------------------------------- | --------------------------- |
123
+ | `"dt"`, `"li"`, `"div"` | Tag names |
124
+ | `":model(flow)"`, `":model(phrasing)"` | Content category references |
125
+ | `":not(:model(interactive), a)"` | Negation pseudo-class |
126
+ | `":has(:model(interactive))"` | Has pseudo-class |
127
+ | `"#text"` | Text nodes |
128
+ | `"#custom"` | Custom elements |
129
+ | `"svg\|a"`, `"svg\|circle"` | SVG namespace elements |
130
+ | `"link[itemprop]"` | Attribute selectors |
131
+
132
+ ## Global Attributes
133
+
134
+ The `globalAttrs` object maps category names (prefixed with `#`) to inclusion rules:
135
+
136
+ | Value | Meaning |
137
+ | ---------- | ---------------------------------------------------- |
138
+ | `true` | Include all attributes from the category |
139
+ | `false` | Exclude the category entirely |
140
+ | `string[]` | Include only the listed attributes from the category |
141
+
142
+ ```json
143
+ "globalAttrs": {
144
+ "#HTMLGlobalAttrs": true,
145
+ "#GlobalEventAttrs": true,
146
+ "#ARIAAttrs": true,
147
+ "#HTMLLinkAndFetchingAttrs": ["href", "target", "download", "ping", "rel"]
148
+ }
149
+ ```
150
+
151
+ ### Available Categories (19)
152
+
153
+ | Category | Description |
154
+ | ----------------------------------- | ------------------------------------------------------------------- |
155
+ | `#HTMLGlobalAttrs` | Standard HTML global attributes (accesskey, class, id, style, etc.) |
156
+ | `#GlobalEventAttrs` | Event handler attributes (onclick, onload, onfocus, etc.) |
157
+ | `#ARIAAttrs` | ARIA attributes (aria-\*, role) |
158
+ | `#HTMLLinkAndFetchingAttrs` | Link and fetch attributes (href, target, download, etc.) |
159
+ | `#HTMLEmbededAndMediaContentAttrs` | Embedded content attributes (src, height, width, etc.) |
160
+ | `#HTMLFormControlElementAttrs` | Form control attributes (autocomplete, disabled, name, etc.) |
161
+ | `#HTMLTableCellElementAttrs` | Table cell attributes (colspan, rowspan, headers, etc.) |
162
+ | `#SVGAnimationAdditionAttrs` | SVG animation addition attributes |
163
+ | `#SVGAnimationAttributeTargetAttrs` | SVG animation attribute target attributes |
164
+ | `#SVGAnimationEventAttrs` | SVG animation event attributes |
165
+ | `#SVGAnimationTargetElementAttrs` | SVG animation target element attributes |
166
+ | `#SVGAnimationTimingAttrs` | SVG animation timing attributes |
167
+ | `#SVGAnimationValueAttrs` | SVG animation value attributes |
168
+ | `#SVGConditionalProcessingAttrs` | SVG conditional processing attributes |
169
+ | `#SVGCoreAttrs` | SVG core attributes (id, tabindex, lang, class, style, etc.) |
170
+ | `#SVGFilterPrimitiveAttrs` | SVG filter primitive attributes |
171
+ | `#SVGPresentationAttrs` | SVG presentation attributes (fill, stroke, transform, etc.) |
172
+ | `#SVGTransferFunctionAttrs` | SVG transfer function attributes |
173
+ | `#XLinkAttrs` | XLink attributes (deprecated) |
174
+
175
+ ## Element-Specific Attributes
176
+
177
+ The `attributes` object maps attribute names to definitions. Each definition may contain:
178
+
179
+ | Field | Type | Description |
180
+ | -------------- | -------------------- | ----------------------------------------------- |
181
+ | `type` | various | Attribute value type (see below) |
182
+ | `condition` | `string \| string[]` | CSS selector(s) for when the attribute is valid |
183
+ | `required` | `boolean` | Whether the attribute is required |
184
+ | `defaultValue` | `string` | Default value |
185
+ | `description` | `string` | Human-readable description |
186
+ | `animatable` | `boolean` | Whether the attribute is animatable (SVG) |
187
+ | `deprecated` | `boolean` | Deprecated status flag (see below) |
188
+ | `obsolete` | `boolean` | Obsolete status flag (see below) |
189
+ | `experimental` | `boolean` | Experimental status flag (see below) |
190
+ | `nonStandard` | `boolean` | Non-standard status flag (see below) |
191
+
192
+ ### Status Flags
193
+
194
+ These boolean flags indicate the standardization status of an attribute:
195
+
196
+ | Flag | Meaning |
197
+ | -------------- | ------------------------------------------------------------------------------------------------------------- |
198
+ | `experimental` | Part of an emerging specification that is not yet stable. Browsers may have partial or prefixed support. |
199
+ | `deprecated` | Officially discouraged by the specification. Still recognized by browsers but should not be used in new code. |
200
+ | `obsolete` | Removed from the specification entirely. May not be recognized by modern browsers at all. |
201
+ | `nonStandard` | Not part of any W3C or WHATWG specification. Vendor-specific or proprietary. |
202
+
203
+ **`deprecated` vs `obsolete`:** `deprecated` means the spec still defines the attribute
204
+ but discourages its use (e.g., `<table border>`). `obsolete` means the attribute has been
205
+ removed from the specification altogether (e.g., legacy presentational attributes on
206
+ elements that have been fully obsoleted). In practice, `deprecated` attributes usually
207
+ still work in browsers, while `obsolete` attributes may not.
208
+
209
+ **Flag precedence (spec > MDN):** When a flag is set in the manual spec file
210
+ (`src/spec.*.json`), it takes precedence over the MDN-scraped value. This allows you to
211
+ correct cases where MDN data is inaccurate or lagging behind the specification. Flags
212
+ from MDN are used only when the manual spec does not define the attribute or does not
213
+ set that particular flag.
214
+
215
+ **When to set flags manually:**
216
+
217
+ - MDN flags an attribute as `experimental` but the spec has stabilized it -- set
218
+ `"experimental": false` (or omit it) in the spec file to override
219
+ - An attribute is deprecated in the spec but MDN has not yet updated -- set
220
+ `"deprecated": true` in the spec file
221
+ - A vendor-specific attribute needs to be marked -- set `"nonStandard": true`
222
+
223
+ ### Attribute Value Types
224
+
225
+ **Simple string types:**
226
+
227
+ `"String"`, `"URL"`, `"Boolean"`, `"DOMID"`, `"Any"`, `"Number"`, `"Pattern"`,
228
+ `"OneLineAny"`, `"CustomElementName"`, `"DateTime"`
229
+
230
+ **Enumerated type:**
231
+
232
+ ```json
233
+ {
234
+ "enum": ["hidden", "text", "search"],
235
+ "invalidValueDefault": "text",
236
+ "missingValueDefault": "text",
237
+ "caseInsensitive": true,
238
+ "disallowToSurroundBySpaces": true,
239
+ "sameStates": { "none": ["off"] }
240
+ }
241
+ ```
242
+
243
+ | Field | Description |
244
+ | ---------------------------- | ------------------------------------------- |
245
+ | `enum` | Array of allowed string values |
246
+ | `invalidValueDefault` | Default state when the value is invalid |
247
+ | `missingValueDefault` | Default state when the attribute is missing |
248
+ | `caseInsensitive` | Whether matching is case-insensitive |
249
+ | `disallowToSurroundBySpaces` | Whether surrounding spaces are disallowed |
250
+ | `sameStates` | Maps canonical values to their aliases |
251
+
252
+ **Token list type:**
253
+
254
+ ```json
255
+ { "token": "Accept", "separator": "comma", "unique": true, "caseInsensitive": true }
256
+ ```
257
+
258
+ | Field | Description |
259
+ | ----------------- | ------------------------------------------------ |
260
+ | `token` | Token type name |
261
+ | `separator` | `"space"` or `"comma"` |
262
+ | `unique` | Whether tokens must be unique |
263
+ | `caseInsensitive` | Whether matching is case-insensitive |
264
+ | `ordered` | Whether order matters |
265
+ | `number` | Cardinality: `"zeroOrMore"`, `"oneOrMore"`, etc. |
266
+
267
+ **Number constraint type:**
268
+
269
+ ```json
270
+ { "type": "integer", "gt": 0 }
271
+ ```
272
+
273
+ Fields: `type` (`"integer"` or `"float"`), `gt`, `gte`, `lt`, `lte` for bounds.
274
+
275
+ **Union type (array):** Multiple valid types -- `["DateTime", "Number"]` or
276
+ `["<svg-length>", "<percentage>"]`.
277
+
278
+ **CSS/SVG value types:** Angle-bracket strings like `"<text-coordinate>"`,
279
+ `"<svg-length>"`, `"<percentage>"`, `"<list-of-numbers>"`, `"<number>"`.
280
+
281
+ ### Conditional Attributes
282
+
283
+ The `condition` field restricts an attribute to specific element states. It accepts a
284
+ single CSS selector string or an array. The `i` flag enables case-insensitive matching:
285
+
286
+ ```json
287
+ "accept": { "type": { "token": "Accept", "separator": "comma" }, "condition": "[type='file' i]" },
288
+ "checked": { "type": "Boolean", "condition": ["[type='checkbox' i]", "[type='radio' i]"] }
289
+ ```
290
+
291
+ ## ARIA Integration
292
+
293
+ The `aria` object defines ARIA role and property integration.
294
+
295
+ ### Top-Level ARIA Fields
296
+
297
+ | Field | Type | Description |
298
+ | ------------------ | ------------------------------------- | -------------------------------------- |
299
+ | `implicitRole` | `string \| false` | Default ARIA role, or `false` for none |
300
+ | `permittedRoles` | `true \| false \| string[] \| object` | Allowed explicit roles |
301
+ | `namingProhibited` | `boolean` | Whether accessible name is prohibited |
302
+ | `properties` | `object \| false` | ARIA property constraints |
303
+ | `conditions` | `object` | Context-dependent ARIA overrides |
304
+
305
+ ### Permitted Roles
306
+
307
+ - `true` -- Any role is permitted
308
+ - `false` -- No explicit roles permitted
309
+ - `string[]` -- Specific permitted role names
310
+ - Object with AAM references (SVG): `{ "core-aam": true, "graphics-aam": true }`
311
+
312
+ Role entries can also be objects: `{ "name": "directory", "deprecated": true }`.
313
+
314
+ ### ARIA Properties
315
+
316
+ | Field | Type | Description |
317
+ | --------- | ------------------- | -------------------------------- |
318
+ | `global` | `boolean` | Include global ARIA properties |
319
+ | `role` | `boolean \| string` | Include role-specific properties |
320
+ | `without` | `array` | Property restriction rules |
321
+
322
+ When `properties` is `false`, no ARIA properties are allowed (e.g., `input[type=hidden]`).
323
+
324
+ ### Property Restrictions (`without`)
325
+
326
+ | Field | Type | Description |
327
+ | ------- | -------- | ---------------------------------------------------- | ------------------------------------ |
328
+ | `type` | `string` | `"must-not"`, `"should-not"`, or `"not-recommended"` |
329
+ | `name` | `string` | ARIA property name (e.g., `"aria-checked"`) |
330
+ | `value` | `string` | Optional: restrict only for this specific value |
331
+ | `alt` | `object` | Optional: `{ "method": "set-attr" | "remove-attr", "target": "<attr>" }` |
332
+
333
+ ### Conditional ARIA (`conditions`)
334
+
335
+ Maps CSS selectors to ARIA overrides. Condition keys use the same CSS selector syntax:
336
+
337
+ - `":not([href])"` -- element without `href`
338
+ - `"[type='button' i]"` -- input with `type="button"`
339
+ - `"dl > div"` -- div as direct child of dl
340
+ - `"[type='checkbox' i][aria-pressed]"` -- compound selectors
341
+
342
+ ### Version-Specific ARIA Overrides
343
+
344
+ Use version keys (`"1.1"`, `"1.2"`) as sibling properties to override ARIA for specific
345
+ spec versions. Version overrides can contain their own `conditions` object:
346
+
347
+ ```json
348
+ "aria": {
349
+ "implicitRole": "paragraph",
350
+ "permittedRoles": true,
351
+ "1.1": { "implicitRole": false }
352
+ }
353
+ ```
354
+
355
+ ## Common Files
356
+
357
+ ### spec-common.attributes.json
358
+
359
+ Defines the 19 global attribute categories. Each category maps attribute names to
360
+ definitions using the same format described in the attributes section.
361
+
362
+ ### spec-common.contents.json
363
+
364
+ Defines content model category macros referenced via `:model()`. Structure:
365
+
366
+ ```json
367
+ { "models": { "#metadata": ["base", "link", ...], "#flow": ["a", "abbr", ...], ... } }
368
+ ```
369
+
370
+ ### Content Model Categories
371
+
372
+ **HTML categories (10):**
373
+
374
+ | Category | Description |
375
+ | -------------------- | ---------------------------------------------------------- |
376
+ | `#metadata` | Document metadata elements (base, link, meta, etc.) |
377
+ | `#flow` | Most body-level elements |
378
+ | `#sectioning` | Section elements (article, aside, nav, section) |
379
+ | `#heading` | Heading elements (h1-h6, hgroup) |
380
+ | `#phrasing` | Inline-level elements |
381
+ | `#embedded` | Embedded content (audio, canvas, embed, iframe, img, etc.) |
382
+ | `#interactive` | Interactive elements (a[href], button, details, etc.) |
383
+ | `#palpable` | Elements that render visible content |
384
+ | `#script-supporting` | Script-supporting elements (script, template) |
385
+
386
+ **SVG categories (19):**
387
+
388
+ | Category | Description |
389
+ | -------------------------- | ----------------------------------------------------------------- |
390
+ | `#SVGAnimation` | Animation elements (animate, animateMotion, set, etc.) |
391
+ | `#SVGBasicShapes` | Basic shape elements (circle, ellipse, line, polygon, etc.) |
392
+ | `#SVGContainer` | Container elements (a, defs, g, marker, svg, symbol, etc.) |
393
+ | `#SVGDescriptive` | Descriptive elements (desc, metadata, title) |
394
+ | `#SVGFilterPrimitive` | Filter primitive elements (feBlend, feColorMatrix, etc.) |
395
+ | `#SVGFont` | Font elements (font, font-face, glyph, etc.) |
396
+ | `#SVGGradient` | Gradient elements (linearGradient, radialGradient, stop) |
397
+ | `#SVGGraphics` | Graphics elements (circle, image, path, text, use, etc.) |
398
+ | `#SVGGraphicsReferencing` | Graphics referencing elements (use) |
399
+ | `#SVGLightSource` | Light source elements (feDistantLight, fePointLight, feSpotLight) |
400
+ | `#SVGNeverRendered` | Never-rendered elements (clipPath, defs, metadata, etc.) |
401
+ | `#SVGPaintServer` | Paint server elements (linearGradient, pattern, etc.) |
402
+ | `#SVGRenderable` | Renderable elements (a, circle, g, svg, text, etc.) |
403
+ | `#SVGShape` | Shape elements (circle, ellipse, line, path, polygon, etc.) |
404
+ | `#SVGStructural` | Structural elements (defs, g, svg, symbol, use) |
405
+ | `#SVGStructurallyExternal` | Structurally external elements (currently empty) |
406
+ | `#SVGTextContent` | Text content elements (text, textPath, tspan, etc.) |
407
+ | `#SVGTextContentChild` | Text content child elements (textPath, tspan, etc.) |
408
+
409
+ ## Full Examples
410
+
411
+ ### Simple HTML Element -- `<p>`
412
+
413
+ ```json
414
+ // https://html.spec.whatwg.org/multipage/grouping-content.html#the-p-element
415
+ {
416
+ "contentModel": {
417
+ "contents": [{ "oneOrMore": ":model(phrasing)" }]
418
+ },
419
+ "globalAttrs": {
420
+ "#HTMLGlobalAttrs": true,
421
+ "#GlobalEventAttrs": true,
422
+ "#ARIAAttrs": true
423
+ },
424
+ "attributes": {},
425
+ "aria": {
426
+ "implicitRole": "paragraph",
427
+ "permittedRoles": true,
428
+ "namingProhibited": true,
429
+ "1.1": { "implicitRole": false }
430
+ }
431
+ }
432
+ ```
433
+
434
+ ### Complex HTML Element -- `<a>`
435
+
436
+ ```json
437
+ // https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element
438
+ {
439
+ "contentModel": {
440
+ "contents": [
441
+ { "transparent": ":not(:model(interactive), a, [tabindex], :has(:model(interactive), a, [tabindex]))" }
442
+ ]
443
+ },
444
+ "globalAttrs": {
445
+ "#HTMLGlobalAttrs": true,
446
+ "#GlobalEventAttrs": true,
447
+ "#ARIAAttrs": true,
448
+ "#HTMLLinkAndFetchingAttrs": ["href", "target", "download", "ping", "rel", "hreflang", "type", "referrerpolicy"]
449
+ },
450
+ "attributes": {},
451
+ "aria": {
452
+ "implicitRole": "link",
453
+ "permittedRoles": ["button", "checkbox", "menuitem", "option", "radio", "switch", "tab", "treeitem"],
454
+ "properties": {
455
+ "global": true,
456
+ "role": true,
457
+ "without": [
458
+ {
459
+ "type": "not-recommended",
460
+ "name": "aria-disabled",
461
+ "value": "true",
462
+ "alt": { "method": "remove-attr", "target": "href" }
463
+ }
464
+ ]
465
+ },
466
+ "conditions": {
467
+ ":not([href])": { "implicitRole": "generic", "permittedRoles": true, "namingProhibited": true }
468
+ },
469
+ "1.1": { "conditions": { ":not([href])": { "implicitRole": false } } }
470
+ }
471
+ }
472
+ ```
473
+
474
+ ### SVG Element -- `svg:text`
475
+
476
+ ```json
477
+ // https://svgwg.org/svg2-draft/text.html#TextElement
478
+ {
479
+ "contentModel": {
480
+ "contents": [
481
+ {
482
+ "zeroOrMore": [
483
+ "#text",
484
+ ":model(SVGAnimation)",
485
+ ":model(SVGDescriptive)",
486
+ ":model(SVGPaintServer)",
487
+ ":model(SVGTextContentChild)",
488
+ "svg|a",
489
+ "svg|clipPath",
490
+ "svg|marker",
491
+ "svg|mask",
492
+ "svg|script",
493
+ "svg|style"
494
+ ]
495
+ }
496
+ ]
497
+ },
498
+ "globalAttrs": {
499
+ "#HTMLGlobalAttrs": true,
500
+ "#GlobalEventAttrs": true,
501
+ "#ARIAAttrs": true,
502
+ "#SVGConditionalProcessingAttrs": ["requiredExtensions", "systemLanguage"],
503
+ "#SVGCoreAttrs": ["id", "tabindex", "autofocus", "lang", "xml:space", "class", "style"],
504
+ "#SVGPresentationAttrs": ["alignment-baseline", "fill", "font-size", "stroke", "transform"]
505
+ },
506
+ "attributes": {
507
+ "x": { "type": "<text-coordinate>", "defaultValue": "0", "animatable": true },
508
+ "y": { "type": "<text-coordinate>", "defaultValue": "0", "animatable": true },
509
+ "textLength": { "type": ["<svg-length>", "<percentage>"], "animatable": true },
510
+ "lengthAdjust": {
511
+ "type": { "enum": ["spacing", "spacingAndGlyphs"] },
512
+ "defaultValue": "spacing",
513
+ "animatable": true
514
+ }
515
+ },
516
+ "aria": {
517
+ "implicitRole": "group",
518
+ "permittedRoles": { "core-aam": true, "graphics-aam": true }
519
+ }
520
+ }
521
+ ```