Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Engineering a Design System

Engineering a Design System

While plenty is said about design system theory, organizational adoption, and visual design, you won’t get very far without solid engineering practices, sound architecture, and the right technologies.

We’ll take a tour through the engineering of TELUS digital’s React-based design system developed over the past year, examining technologies such as CSS Modules; testing practices; architectural decision points; and strategies for scaling to dozens of teams and hundreds of users.

Whether you are at a small or large scale, just starting or far along with a design system, you can immediately apply the lessons and technologies from this talk to your own work.

Ryan Oglesby

April 24, 2018
Tweet

More Decks by Ryan Oglesby

Other Decks in Technology

Transcript

  1. Engineering for stability by crea(ng inten(onal APIs and
 tes(ng first

    and o5en. Engineering for scale ↔ by automa(ng documenta(on and op(mizing package distribu(on.
  2. 
 // Button.scss .button { padding: 1rem; border-radius: 4px; color:

    white; } .button--primary { background-color: $color-primary; } .button--secondary { background-color: $color-secondary; } 
 <Button className="button--primary"> Shop now </Button> <Button className="button--secondary"> Learn more </Button> class Button extends React.Component { render() { // a branded HTML button } } Design system Applica8on Output
  3. 
 // Button.scss .button { padding: 1rem; border-radius: 4px; color:

    white; } .button--primary { background-color: $color-primary; } .button--secondary { background-color: $color-secondary; } 
 // MySquareButton.scss 
 .button { border-radius: 0; color: black; } .my-square-button { background-color: hotpink; } 
 <Button className=“my-square-button"> Find a store </Button> class Button extends React.Component { render() { // a branded HTML button } } Design system Applica8on 
 // Button.scss .button { padding: 1rem; border-radius: 4px; color: white; } .button--primary { background-color: $color-primary; } .button--secondary { background-color: $color-secondary; } Output
  4. // Button.scss .button { // core button styles } .primary

    { composes: button; background-color: $color-primary; } .secondary { composes: button; background-color: $color-secondary; } import styles from './Button.scss' class Button extends React.Component { render() { return ( <button className={styles[this.props.variant]}> {this.props.children} </button> ) } }
  5. // Button.scss .button { // core button styles } .primary

    { composes: button; background-color: $color-primary; } .secondary { composes: button; background-color: $color-secondary; } import styles from './Button.scss' class Button extends React.Component { render() { return ( <button className={styles[this.props.variant]}> {this.props.children} </button> ) } } // compiled Button.css .TDS_Button__button___2kyCB { ... } 
 .TDS_Button__primary___3Xb72 { ... } .TDS_Button__secondary___2qhCP { ... }
  6. 
 $ yarn test:e2e @tds/core-button yarn run v1.5.1 $ node

    scripts/e2e.js Started chromedriver [Components Spec] Test Suite ================================ Running: @tds/core-button ✔ Element <#button> was visible after 31 milliseconds. ✔ Passed [ok]: 9 aXe Tests Passed ✖ Failed [fail]: (Elements must have sufficient color contrast [<div class="TDS_Flexbox-modules__row___DL6tZ TDS_BaseButton-modules__centered___3Xkio TDS_Borders-modules__rounded___1Qg-s">Submit</div>]) - expected "https:// dequeuniversity.com/rules/axe/2.6/color-contrast?application=axeAPI" but got: "policy violation" at endReadableNT (_stream_readable.js:1101:12) ✖ #app passes accessibility scan - expected "true" but got: "false" at Object.checkAccessibility (/Users/ryanoglesby/Projects/telus-digital/tds/ e2e/commands/checkAccessibility.js:4:35) FAILED: 2 assertions failed and 2 passed (1.145s)

  7. $ yarn test:e2e @tds/core-notification yarn run v1.5.1 $ node scripts/e2e.js

    Started chromedriver [Components Spec] Test Suite ================================ Running: @tds/core-notification ✖ Screenshots Match Failed for chrome_headless.png with a tolerance of 0%, actual was 1.07%. - expected "0" but got: "1.07" at Object.takeScreenshot (/Users/ryanoglesby/Projects/telus-digital/tds/e2e/ commands/compareScreenshot.js:55:17) at Object.browser.saveScreenshot (/Users/ryanoglesby/Projects/telus-digital/ tds/e2e/commands/compareScreenshot.js:38:16) at FSReqWrap.oncomplete (fs.js:153:20) FAILED: 1 assertions failed and 3 passed (3.314s) _________________________________________________ TEST FAILURE: 1 assertions failed, 3 passed. (3.378s)
  8. Engineering for stability by crea(ng inten(onal APIs and
 tes(ng first

    and o5en. Engineering for scale ↔ by automa(ng documenta(on and op(mizing package distribu(on.
  9. • Correct and trustworthy • Comprehensive • Component APIs (props,

    data types, allowed values) • Live examples Documenta8on must-haves React Styleguidist Storybook
  10. /** * A standard, branded clickable element for use in

    forms mainly. * @version 1.0.2 */ class Button extends React.Component { render() { // ... } } Button.propTypes = { /** * The HTML button type. * @see See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/ Element/button) for more info. */ type: PropTypes.oneOf(['button', 'submit', 'reset']), /** * The style. */ variant: PropTypes.oneOf(['primary', 'secondary', 'inverted']), /** * The label. */ children: PropTypes.string.isRequired, } Button.defaultProps = { type: 'button', variant: 'primary', }
  11. Provide a function as the `onClick` prop to perform an

    action when clicked. **Avoid using a button if navigation is the primary action, as a [Link](#link) is more appropriate.** ### Best practices * Aim to use only one primary button per page * Keep the label short and able to fit on a single line * The label should clearly describe the action By default, Buttons will be displayed in the `primary` variant. Use primary buttons for the main action on a page or in a form. ``` <Button>Submit</Button> ``` Specify the `variant` to create a button for secondary actions. ``` <Button variant=“secondary”>Find out more</Button> ```
  12. import { Button } from '@telusdigital/tds' import '@telusdigital/tds/dist/index.css' class HelloWorldApp

    extends React.Component { render() { return ( <Button onClick={this.props.sayHello}>Hello world</Button> ); } } export default HelloWorldApp 
 { "name": "@telusdigital/hello-world-app", "version": “0.1.0", "description": "An app that says hello", "dependencies": { "@telusdigital/tds": "^1.0.0" } }
  13. import { Button } from '@telusdigital/tds' import '@telusdigital/tds/dist/index.css' class HelloWorldApp

    extends React.Component { render() { return ( <Button onClick={this.props.sayHello}>Hello world</Button> ); } } export default HelloWorldApp 
 { "name": "@telusdigital/hello-world-app", "version": “0.1.0", "description": "An app that says hello", "dependencies": { "@telusdigital/tds": "^1.0.0" } } “^2.0.0" .TDS_Button__primary___3Xb72 { /* ... */ } .TDS_Button__secondary___2qhCP { /* ... */ } .TDS_Text__base___2nt0g { /* ... */ } .TDS_Box__spacing2___3Z6ro { /* ... */ }
  14. • Consolidate the lint, build, test, and release process •

    Easier to coordinate changes across mul<ple components • Single place to report issues • Easier to generate documenta<on Why a monorepo?
  15. import Box from '@tds/core-box' import Button from '@tds/core-button' class HelloWorldApp

    extends React.Component { render() { return ( <Box inset={3}> <Button onClick={this.props.sayHello}>Hello world</Button> </Box> ); } } export default HelloWorldApp 
 { "name": "@telusdigital/hello-world-app", "version": “0.1.0", "description": "An app that says hello", "dependencies": { “@tds/core-box": “^1.0.2”, “@tds/core-button": "^1.1.0" } }
  16. React CSS Modules ✅ jest + enzyme + enzyme-matchers nightwatch

    + nightwatch-accessibility (axe-core) resemble-js react-styleguidist lerna Key technology