Contract: Groups
Groups
Groups organize variables into nested structures. Groups can extend other groups to inherit their structure.
Requirements
- Groups MUST be objects that contain groups and/or variables.
- Groups MUST NOT include
$typeor$value. $extendsis allowed only on groups.
Group structure
A group is an object that contains nested groups and/or variables. Groups do not have $type or $value properties.
Example:
{
"color": {
"text": {
"primary": {
"$type": "color",
"$value": "#000000"
},
"secondary": {
"$type": "color",
"$value": "#666666"
}
}
}
}
In this example, color and color.text are groups. color.text.primary and color.text.secondary are variables.
Group extension with $extends
Groups can extend other groups using DTCG 2025.10 $extends syntax. This allows inheritance and composition.
Rules:
$extendsMUST use curly brace syntax:"{group.path}"- The referenced group MUST exist
- Extending groups inherit all variables and nested groups from the referenced group
- Extending groups can override inherited variables
- Extending groups can add new variables
Example:
{
"color": {
"base": {
"primary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0, 0.4, 0.8],
"hex": "#0066cc"
}
},
"secondary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.4, 0.4, 0.4],
"hex": "#666666"
}
}
},
"brand": {
"$extends": "{color.base}",
"primary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1, 0, 0.4],
"hex": "#ff0066"
}
},
"accent": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0, 1, 0.4],
"hex": "#00ff66"
}
}
}
}
}
After resolution, color.brand contains:
color.brand.primary(overridden, value is#ff0066)color.brand.secondary(inherited fromcolor.base.secondary)color.brand.accent(new variable)
Note: DTCG 2025.10 also supports JSON Pointer syntax ($ref with #/path/to/group) for compatibility with JSON Schema tooling. Variable Design Standard (VDS) recommends $extends with curly brace syntax to align with variable references.
Group properties
Groups can have properties that apply to the group:
$deprecated: marks the group and all nested variables as deprecated$extensions: stores non-standard metadata for the group
Example:
{
"color": {
"$deprecated": true,
"$extensions": {
"tool": {
"id": "collection-123"
}
},
"old": {
"$type": "color",
"$value": "#000000"
}
}
}
Empty groups
Groups can be empty (contain no variables or nested groups). Empty groups are allowed but SHOULD be removed if they serve no purpose.
Inheritance semantics
When a group extends another group:
- All variables from the referenced group are copied into the extending group
- All nested groups from the referenced group are copied into the extending group
- Variables in the extending group override inherited variables with the same name
- Nested groups in the extending group merge with inherited nested groups
Merge precedence rules
Group extension follows explicit precedence rules:
Rule 1: Local definitions override inherited
Variables and nested groups defined in the extending group take precedence over inherited definitions with the same name.
Example:
{
"color": {
"base": {
"primary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0, 0.4, 0.8],
"hex": "#0066cc"
}
}
},
"brand": {
"$extends": "{color.base}",
"primary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1, 0, 0.4],
"hex": "#ff0066"
}
}
}
}
}
After resolution, color.brand.primary uses the local definition (#ff0066), not the inherited one (#0066cc).
Rule 2: Nested groups merge (don’t replace)
When both the extending group and the referenced group have a nested group with the same name, the nested groups merge rather than replace.
Example:
{
"color": {
"base": {
"gray": {
"100": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.96, 0.96, 0.96],
"hex": "#f5f5f5"
}
}
}
},
"brand": {
"$extends": "{color.base}",
"gray": {
"200": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.9, 0.9, 0.9],
"hex": "#e6e6e6"
}
}
}
}
}
}
After resolution, color.brand.gray contains both gray.100 (inherited) and gray.200 (local).
Rule 3: Variables override inherited variables
If a variable exists in both the referenced group and the extending group with the same name, the extending group’s variable completely replaces the inherited variable (no merging of variable properties).
Conflict resolution
Variable name conflicts
When the extending group defines a variable with the same name as an inherited variable:
- Resolution: The extending group’s variable takes precedence
- Behavior: The inherited variable is completely replaced (not merged)
- Validation: This is allowed and expected behavior
Example:
{
"spacing": {
"base": {
"medium": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" }
}
},
"mobile": {
"$extends": "{spacing.base}",
"medium": {
"$type": "dimension",
"$value": { "value": 12, "unit": "px" }
}
}
}
}
After resolution, spacing.mobile.medium is 12px (local), not 16px (inherited).
Nested group name conflicts
When both groups have a nested group with the same name:
- Resolution: Nested groups merge (variables from both are included)
- Behavior: Variables in the local nested group override inherited variables with the same name
- Validation: This is allowed and expected behavior
Explicit prohibitions
Groups with $extends MUST follow these rules:
Prohibition 1: No $type or $value at group level
A group that uses $extends MUST NOT have $type or $value properties at the same level.
Invalid:
{
"color": {
"brand": {
"$extends": "{color.base}",
"$type": "color", // PROHIBITED
"$value": "#0066cc" // PROHIBITED
}
}
}
Valid:
{
"color": {
"brand": {
"$extends": "{color.base}",
"primary": {
"$type": "color", // Allowed - this is a variable, not the group
"$value": "#0066cc"
}
}
}
}
Prohibition 2: No circular references
Groups MUST NOT create circular references through $extends. See Circular reference prevention for details.
Prohibition 3: $extends target must exist
The group referenced by $extends MUST exist. Validation MUST reject groups that extend non-existent groups.
Circular reference prevention
Groups MUST NOT create circular references through $extends. If group A extends group B, group B MUST NOT extend group A (directly or indirectly).
Example of invalid circular reference:
{
"color": {
"a": {
"$extends": "{color.b}"
},
"b": {
"$extends": "{color.a}"
}
}
}
This MUST be rejected during validation.
Reference resolution and evaluation
Group extension is resolved before variable reference resolution. The resolution order is:
- Resolve all
$extendsin groups - Resolve all variable references (
{path}or#/path)
Examples
Basic group structure
{
"spacing": {
"small": {
"$type": "dimension",
"$value": { "value": 8, "unit": "px" }
},
"medium": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" }
},
"large": {
"$type": "dimension",
"$value": { "value": 24, "unit": "px" }
}
}
}
Group extension with override
{
"spacing": {
"base": {
"small": {
"$type": "dimension",
"$value": { "value": 8, "unit": "px" }
},
"medium": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" }
}
},
"mobile": {
"$extends": "{spacing.base}",
"small": {
"$type": "dimension",
"$value": { "value": 4, "unit": "px" }
}
}
}
}
After resolution, spacing.mobile contains:
spacing.mobile.small(overridden, value is4px)spacing.mobile.medium(inherited, value is16px)
Complex hierarchical structure
{
"color": {
"palette": {
"gray": {
"100": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.96, 0.96, 0.96],
"hex": "#f5f5f5"
}
},
"900": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.1, 0.1, 0.1],
"hex": "#1a1a1a"
}
}
}
},
"semantic": {
"$extends": "{color.palette}",
"text": {
"primary": {
"$type": "color",
"$value": "{color.palette.gray.900}"
},
"secondary": {
"$type": "color",
"$value": "{color.palette.gray.100}"
}
}
}
}
}
Failure modes
If you ignore group rules:
- Circular references cause infinite resolution loops
- Missing
$reftargets break resolution - Overriding variables incorrectly can break component styling
- Empty groups add noise without value
Validation Checklist
A group is valid if:
- It contains variables, nested groups, or both
- If it uses
$extends, the target exists and is not circular - If it uses
$deprecated, it is a boolean - If it uses
$extensions, it is an object
Out of scope
- Runtime group resolution algorithms (use DTCG-compliant resolvers)
- Group merging strategies beyond override semantics
- Group versioning (handle at variable level)