aesthetic-suite / framework

🎨 Aesthetic is an end-to-end multi-platform styling framework that offers a strict design system, robust atomic CSS-in-JS engine, a structural style sheet specification (SSS), a low-runtime solution, and much more!
https://aestheticsuite.dev
MIT License
203 stars 5 forks source link

RFC: The future, a design system framework #71

Closed milesj closed 4 years ago

milesj commented 4 years ago

History & Present

Aesthetic was built around the time that CSS-in-JS solutions were rising in popularity. It was designed to bridge the gap between current CSS-in-JS libraries and React components, and was not meant to be yet another CSS-in-JS library. As such, Aesthetic offers the adapter pattern where CSS-in-JS libraries are plug-and-play, while not handling the CSS itself.

Besides the above mentioned, Aesthetic aimed to solve the following problems that plagued libraries and applications.

  1. CSS-in-JS libraries require different syntax for defining styles. This could be problematic when switching libraries (for performance or other reasons), as the syntax differs, and would require a potential massive migration (and ejection if a failure). To mitigate this, Aesthetic implements a "unified syntax", where the same syntax works for all adapters.
  2. The other issue that a unified syntax solves is third-party library adoption. If library A styles their components with Aphrodite, and library B styles theirs with Fela, then we have 2 differing and conflicting libraries. This would increase bundle size, reduce interoperability, and more. If both A and B libraries were instead written in Aesthetic, then the underlying adapter (Aphrodite or Fela) can be swapped out without breaking compatibility or increasing bundle sizes.

Outstanding issues

For the most part, Aesthetic works and serves its purpose pretty well. However, it's not perfect, and could use a rewrite to solve the following issues.

Themes are too dynamic

There is no set structure for theme objects, and as such, they cannot be typed safely, nor can they be trusted when interoping with third-party libraries. For example:

// Registered in an application
aesthetic.registerTheme('app-light', {
  colors: {
    white: '#fff',
  },
  unit: 8,
});

// Registered in a library
aesthetic.registerTheme('3rd-party-dark', {
  color: {
    black: ['#000'],
  },
  spacing: 4,
});

In the above example, an application and a third-party library may register a theme, completely independent of each other, with differing structures (unit vs spacing, etc). When used in parallel, components will break when themes change, as the following styles would only work under one theme, not both.

useStyles(theme => ({
  padding: theme.unit * 2,
}));

This actually breaks interoperability and the 2nd adoption problem above. This was a massive oversight on my end when designing the theme layer.

Global styles are complicated to manage

Global styles are primarily applied to html, body, and a, for global inheritance of colors, spacing, and font sizing. This works flawlessly until one of the following occurs:

Component styles are hard to customize

Take the following style sheet for a Button, where it has a primary background with a 1px border (dark blue), and a base text color (white). Seems straight forward right?

useStyles(({ color }) => ({
  button: {
    border: `1px solid ${color.primary[4]}`,
    backgroundColor: color.primary[3],
    color: color.base,
  },
}));

Not really. It's actually very restrictive, isolating, and hard to customize. The above works well for the theme it was initially designed for, in this instance, a "light" theme. But what happens when we change it to a dark theme?

  1. First, the indices are reversed, so 0 is darkest and 10 is lightest. Depending on the component, this will look great, or it will look terrible, and there's no way to change it based on theme, since the CSS/syntax is hard-coded in the component.
  2. The base color may change from white to black in the dark theme, so now we run into accessibility concerns. Is black text viable on a colored button? Usually not. This is similar to the issue previously mentioned, where we can't change the color property on a theme-by-theme basis.
  3. What if the dark theme wants 2px borders? Or no borders? Or rounded corners? Again, we have no way of handling that.
  4. So on and so forth.

Future

I would love to resolve all the issues mentioned previously, most of which are easily solved with a type-safe and structure-safe theme layer. However, while brainstorming the possibilities, I thought to myself, "Why not take a step back and expand the scope of the project?". What does this mean exactly? Well, in the current state of the web development world, companies are pushing hard and forward with design systems, such as: Airbnb Lunar/DLS, Google Material, GitHub Primer, SalesForce Lightning, Mozilla Photon, Shopify Polaris, IBM Carbon, so on and so forth.

Every company is building their design system from scratch, with different technologies, duplicated across many platforms. What if there was a technological solution to this problem? This is where Aesthetic comes in. What if Aesthetic was re-purposed to be a "design system framework", where a design system's fundamentals are configured (fonts, spacing, borders, shadows, breakpoints, interactions, etc), are compiled to multiple target platforms or technologies (CSS, Sass, Less, JS/TS, iOS, Android, React Native, etc), and is ultimately robust and easy enough to be used by any company.

How it works

I've been researching existing design systems for commonalities (Google doc), as a means to build a foundation for this framework. After a bit of research, and minor technical prototyping, I believe the initial step forward would be to use YAML for configuring a design system, while adhering to the following fundamentals.

An example of that YAML file is as follows, with descriptive comments.

# Whether the design system focuses on mobile or desktop first.
# This settings will control various features, like breakpoints.
# Accepts "mobile-first" or "desktop-first".
strategy: mobile-first

# List of 3-5 breakpoints for responsive and adaptive support. If not provided,
# will default to the following 5 values.
breakpoints:
  - 0
  - 600
  - 960
  - 1280
  - 1920

# Spacing related settings.
spacing:
  # The algorithm used for spacing and page density calculations. Accepts the following:
  #   vertical-rhythm - Calculates font size + line height for spacing.
  #   unit - Uses an explicit pixel unit value for spacing.
  type: vertical-rhythm

  # Explicit spacing unit (in pixels).
  unit: 8

# Text and font related settings.
typography:
  # Font family for the entire system. If not provided, defaults to the OS font.
  fontFamily: 'Roboto'

  # Root font size (in pixels).
  fontSize: 16

  # Factor to increase (mobile-first) or decrease (desktop-first) root font size each breakpoint.
  fontScale: 1.1

  # Factor to increase font size for each text heading.
  headingScale: 1.25

  # Root line height.
  lineHeight: 1.5

# Border related settings.
border:
  # Rounded corner radius (in pixels).
  radius: 3

  # Factor to increase radius each size.
  radiusScale: 1

  # Width of the border.
  width: 1

  # Factor to increase width each size.
  widthScale: 1

# Shadow and depth related settings.
shadow:
  # Depth Y offset (in pixels).
  depth: 2

  # Factor to increase depth each size.
  depthScale: 1

  # Blur radius (in pixels).
  blur: 2

  # Factor to increase blur each size.
  blurScale: 1.25

  # Spread radius (in pixels).
  spread: 0

  # Factor to increase spread each size.
  spreadScale: 0

# List of all color names that each theme must implement.
colors:
  - red
  - blue
  - green
  - ...

# Mapping of themes and their colors.
themes:
  # A light theme with a custom name.
  foo:
    # Base color scheme for this theme. Accepts "light" or "dark".
    # Used in `prefers-color-scheme` browser detection.
    scheme: light

    # Mapping of all colors in the theme, with a range of 10 hexcodes per color,
    # with 400 being the base default color, and the bounds going from light to dark,
    # or dark to light if scheme is "dark".
    colors:
      red:
        00: '#FE8484' # Lightest
        10: '#FE8484'
        20: '#FE8484'
        30: '#FE8484'
        40: '#FE8484' # Base
        50: '#FE8484'
        60: '#FE8484'
        70: '#FE8484'
        80: '#FE8484'
        90: '#FE8484' # Darkest

    # Mapping of pre-defined palettes (UI states) to colors and their shades (from above).
    # Consumers will reference these values, instead of colors directly.
    palettes:
      # Priority (properties in order of specificity)
      # TODO - THESE ONLY APPLY TO BACKGROUNDS... WHAT ABOUT FOREGROUND/TEXT?
      primary:
        base: purple.40
        focused: purple.50
        selected: purple.50
        hovered: purple.60
        disabled: gray.40
        failed: red.40
      secondary: # ...
      tertiary: # ...
      neutral: # ...
      # States
      muted: # ...
      danger: # ...
      warning: # ...
      success: # ...
      info: # ...
      # Layout
      text: # ...
      box: # ...
      boxBorder: # ...
      input: # ...
      inputBorder: # ...
      shadow: '#000'

  # A dark theme with a custom name.
  bar:
    scheme: dark
    colors:
      red:
        00: '#FE8484' # Darkest
        10: '#FE8484'
        20: '#FE8484'
        30: '#FE8484'
        40: '#FE8484' # Base
        50: '#FE8484'
        60: '#FE8484'
        70: '#FE8484'
        80: '#FE8484'
        90: '#FE8484' # Lightest

Furthermore, the design system will embrace the following:

References

milesj commented 4 years ago

Moving to wiki: https://github.com/milesj/aesthetic/wiki/The-future,-a-design-system-framework