Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composite Token Composition #179

Closed
CITguy opened this issue Nov 7, 2022 · 3 comments
Closed

Composite Token Composition #179

CITguy opened this issue Nov 7, 2022 · 3 comments
Assignees

Comments

@CITguy
Copy link

CITguy commented Nov 7, 2022

Context

I'm in the process of defining some rather complex typography tokens that define responsive parameters (i.e., values may range from a min to a max, based on viewport/window width). Additionally, the min/max styles have to be defined for (currently) 10 text styles (heading1, headingN, subheadingLg, bodySm, etc.). To make things even more complex, there are three different font families that may be applied to each text style. And to top it all off, we need to be able to mix and match some of these combinations at will.

The issue

The problem I've run into is that I have to duplicate a LOT of composite properties in order to account for every possible combination of font + style + min/max. There are two things that would help with this.

  1. defining partial composite token values (i.e., not having to define all 5 typography token properties), such that I can compose a complete token/style later
  2. being able to extend an existing composite token value, in order to override one or two properties
{
  type: {
    base: {
      $description: 'base typography styles',
      $type: 'typography',
      $value: { // [1] partial composite token definition
        fontFamily: [ "Arial", "sans-serif" ],
        fontWeight: 400,
      },
    },
    heading1-base: {
      $description: 'Heading 1 base styles',
      $value: { // [1] partial composite token definition
        fontSize: "48px",
        lineHeight: "1.25",
        letterSpacing: "0px",
      }
    },
    heading2: {
      $description: 'Heading 2 styles',
      $value: {
        // base props
        $extends: "{type.base}",  // [2] composite extension
        // additional props
        fontSize: "40px",
        lineHeight: "1.2",
        letterSpacing: "-0.5px",
      }
    },
    heading2-alt: {
      $description: 'Heading 2 (alternate) styles',
      $value: {
        // "inherited" parent token props
        $extends: "{type.heading2}", // [2] composite extension
        // child token prop overrides
        $fontFamily: ['Georgia', 'Times', 'serif'], // single prop override
      }
    }
  }
}

This could generate the following Sass code after transformation:

/// base typography styles
$type-base: (
  "fontFamily": (Arial, sans-serif),
  "fontWeight": 400
);
/// Heading 1 base styles
$type-heading1-base: (
  "fontSize": 48px,
  "lineHeight": 1.25,
  "letterSpacing": 0px
);
/// Heading 2 styles
$type-heading2: (
  "fontFamily": (Arial, sans-serif),
  "fontWeight": 400,
  "fontSize": 40px,
  "lineHeight": 1.2,
  "letterSpacing": -0.5px
);
/// Heading 2 (alternate) styles
$type-heading2-alt: (
  "fontFamily": (Georgia, Times, serif),
  "fontWeight": 400,
  "fontSize": 40px,
  "lineHeight": 1.2,
  "letterSpacing": -0.5px
);
  • For times when you need to let consumers control how styles are composed, defining partial tokens would be best.
    • e.g., to get full Heading 1 styles requires $heading1: map.merge($type-base, $type-heading1-base)
  • For times when you want to compose the styles for your consumers, composite token extension is best.
    • e.g., $type-heading2 and $type-heading2-alt
    • if a prop exists in the parent and child, then the child definition takes priority

Questions

  1. Does #/type/base qualify as a $type: "typography" or would we need a different $type?
    • possibly just use $type: 'object' for the partial defs? (not sure how this would affect environment-specific transforms)
  2. Would $type: 'object' essentially be a custom generic composite token?
@CITguy
Copy link
Author

CITguy commented Nov 8, 2022

If built-in composite tokens cannot be modified, I'd recommend introducing *-part variants, such that translation tools can differentiate them from their fully-defined counterparts and apply looser validation requirements.

  • Strict Validation:
    • all $value properties are REQUIRED, unless documented otherwise
    • $value MUST NOT contain extra, unsupported properties
  • Loose Validation:
    • all $value properties are OPTIONAL
    • $value MUST contain at least one valid property
    • $value MUST NOT contain extra, unsupported properties
Strict Validation Loose Validation
strokeStyle strokeStyle-part
border border-part
transition transition-part
shadow shadow-part
gradient gradient-part
typography typography-part
<anotherType> <anotherType>-part

Strengths

  • Naming convention is simple, yet flexible enough to support new, built-in composite token types.
  • Validation strategy allows for any combination of partially-defined composite tokens (infinitely flexible to authors' needs).

Weaknesses

  • No centralized algorithm on how to combine two or more partially-defined composite tokens.
    • Is this the spec's responsibility or the tokens author?

Example: Typography

Valid Definitions

{
  one: {
    $type: 'typography',
    $value: {
      fontFamily: "{alias.to.a.fontFamily}",
      fontWeight: "{alias.to.a.fontWeight}",
      fontSize: "{alias.to.a.dimension}",
      letterSpacing: "{alias.to.a.dimension}",
      lineHeight: "{alias.to.a.string}"
    }
  },

  two: {
    $type: 'typography-part',
    $value: {
      fontFamily: "{alias.to.a.fontFamily}",
      fontWeight: "{alias.to.a.fontWeight}"
    }
  },

  three: {
    $type: 'typography-part',
    $value: {
      fontSize: "{alias.to.a.dimension}",
      letterSpacing: "{alias.to.a.dimension}",
      lineHeight: "{alias.to.a.string}"      
    }
  }
}
  • one defines a full typography token on its own
  • combining two and three results in a fully-defined typography token

Invalid Definitions

{
  four: {
    $type: 'typography',
    $value: {
      foo: 42
    }
  },

  five: {
    $type: 'typography-part',
    $value: {
      foo: 42
    }
  },

  six: {
    $type: 'typography-part',
    $value: {
    }
  }
}
  • four does not define all required properties
  • four defines an unsupported property
  • five does not define any valid properties
  • five defines an unsupported property
  • six does not define any valid properties (empty)
  • no combination of typography-part tokens will result in a fully-defined typography token

@CITguy
Copy link
Author

CITguy commented Nov 17, 2022

FYI: My recent comment on #116 doesn't conflict with the primary topic of this issue, which is to support partial definition of composite tokens. The examples using an $extends key is just an example of what might be possible if the functionality existed in the spec, but ultimately it's to illustrate how composite tokens can be composed from partially-defined tokens.

@kevinmpowell
Copy link
Contributor

@CITguy the editors have reviewed this proposal. It's an interesting idea that could DRY up some token source files, but the level of complexity for vendors to implement is higher than we want to add to the spec at this time.

Alias usage within a composite token will allow "parts" of a composite token to be reused across multiple composite tokens. It will require being more verbose than what you're proposing, but right now we believe the tradeoff in favor of clarity over brevity is where the spec needs to go.

"base-font-family": {
  "$value": ["Arial", "sans-serif"],
  "$type": "fontFamily"
},
"base-font-weight": {
    "$value": 400,
    "$type": "fontWeight"
},
"type-heading-1": {
  "fontFamily": "{base-font-family}",
  "fontSize": "48px",
  "fontWeight": "{base-font-weight}",
  "letterSpacing": "0px",
  "lineHeight": 1.25
},
"type-heading-2": {
  "fontFamily": "{base-font-family}",
  "fontSize": "36px",
  "fontWeight": "{base-font-weight}",
  "letterSpacing": "0px",
  "lineHeight": 1.1
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants