Replies: 3 comments 6 replies
-
Really great writeup, and thanks for the table at the bottom that really helps visualize next steps. I am in agreement that renaming brand-specific components and not namespacing them under another object. Setting up configuration objects for each project is an excellent idea and something we can perhaps move into I'm on board! 🚢 |
Beta Was this translation helpful? Give feedback.
-
There have been a few reasons we haven't moved forward with this, but I wanted to document a specific issue we've run into so we have a record of it and can know when we have the necessary CSS features to move past it. I was breaking out specific pieces of this RFC that we could implement even if we weren't moving forward with full consolidation, and one of them was creating a dev-dependency relationship between the tokens package and the others. Here are the tasks I wrote in that ticket:
Note the problem with the third item. |
Beta Was this translation helpful? Give feedback.
-
Might have found the solution to your media query issue... I was poking around on Adam Argyle's Open Props project and came across syntax for a custom media query. It looks like we do have PostCSS as a dependency in our project and could make use of the custom media query plugin to use CSS vars inside media queries. Thoughts? |
Beta Was this translation helpful? Give feedback.
-
Background
At the moment the CMS Design System consists of a "core" design system and three "child design systems." Today it primarily serves CMS.gov, HealthCare.gov, and Medicare.gov, and each of those domains has its own child design system. The concept of a "child design system" evolved from earlier methods of organizing the design system prior to the formation of a fully staffed, dedicated Experience Team to maintain it. Since then, the way those child design systems are governed and maintained has changed. They went from being independent code repositories to living in a single mono-repo maintained by one team. They went from having separate release processes, timelines, and deployment pipelines to having synchronized releases on a predictable schedule. They went from defining their colors and styles by their deviation from the core design system to having standardized and systemized styles through design tokens. We made these changes to be able to maintain and improve on the quality of our design system despite being only one team with limited capacity and needing to serve a variety of products across multiple domains. These changes have been slowly moving the CMS Design System toward being one design system represented by multiple brands.
However, we are not there yet. Though there are multiple ways we still fall short of that goal, this document will deal with one in particular: Each child design system has its own independent code version. The current version of the core design system is
6.0.0
; for HealthCare.gov, it is10.0.0
; for Medicare.gov, it is8.0.0
; and CMS.gov is still in pre-release. These version numbers show minor and patch versions that are in sync, but their major versions are not. There are actually good reasons to keep those versions independent when the design systems have unique differences. Those reasons have been balanced against arguments in favor synchronization, like how much it would improve our ability to communicate about versions and updates if they were all the same. But now we've reached a tipping point where not only does it makes sense to synchronize, but also code changes have made it much easier than it would have been a year ago. I propose that we to go a step further than version-number synchronization; we should consolidate the design system packages themselves.Proposal
A package can be thought of as a distributable collection of assets. We have one for the core design system and one for each child design system. Teams are meant to use the package that is relevant to them and their domain. For our packages, there is a hierarchical relationship behind the scenes. The child design systems inherit the core, customize it, expand upon it, and get distributed as their own collection of assets available for direct consumption by products in their domain.
Consolidating four packages into one isn't as simple as moving code. There are three primary functions of a child design system that need to be addressed in order to be able to represent all brands by one package:
Styles
Thanks to all the work the team has done in the past year, the styles are now very simple to move.
And that's where we are now. There is one core CSS file with all the style rules and then individual theme CSS files that define the custom properties referenced in those rules. When consolidating packages, we can pretty much just pile them all into the same directory, like so:
The only main difference is that there will be some residual CSS overrides in some of the child design systems that will need to be placed in the theme files for now.
The change for application teams will be only the package they import from. Right now a HealthCare.gov team might import like this:
which would change to this:
Additional integration
Instead of just changing the location where the CSS files get placed during the build step of the
design-system-tokens
package, we can take the opportunity to further improve the integration of the tokens package into the core package and tools. Right now building the tokens package is an implicit prerequisite for building the design system. It creates theme CSS files and copies them into all the relevant directories for each design system plus the docs and Storybook. We've tied several of our build commands together to make sure the tokens build first in most cases, but the relationship isn't as explicit as it could be.If we start importing the theme files from the
@cmsgov/design-system-tokens
package directly, that relationship will be explicit, and we can eliminate duplication of those files. The duplication is made more evident by the fact that multiple copies of the files are checked into version control. I'm not proposing we stop checking in that generated code but that we check in only one copy of each inpackages/design-system-tokens/dist
.JavaScript overrides
There are two main ways we currently take advantage of the fact that each child design system is its own package with its own independent JavaScript entrypoint: To override default configurations and to override default props on specific components. Those two things are conceptually the same, but how we solve for them will be different.
Both kinds of our JavaScript overrides rely on what are called "side effects." A function or module is said to have side effects if calling that function or importing that module changes the state of something else in the application. Our child design system modules have side effects by design. If you import the
TextField
component from@cmsgov/ds-healthcare-gov
, it does more than just hand you back that component. Because everything passes through that one entrypoint (ourindex.ts
that maps to@cmsgov/ds-healthcare-gov
), we can use the import of that module to perform all the necessary side effects to configure the core design system to fit the needs of the HealthCare.gov Child Design System. For instance, the default error placement for HealthCare.gov is below the input fields, so we callsetErrorPlacementDefault('bottom')
. Because we wouldn't have a separate entrypoint when we consolidate down to one package, we'll need to find ways to achieve the same ends without side-effects. That is what we'll explore in the following sections.Overriding default configurations
The design system uses a handful of "flags" to configure various aspects of our JavaScript library. For instance, analytics can be turned on for all
Alert
components by passingtrue
tosetAlertSendsAnalytics
. Right now the HealthCare.gov Design System is the only one left that needs to set custom configs that apply to all applications in its domain, and that's the default error placement configuration. All the other configuration settings are managed by individual applications using the design system. I propose that the one remaining setting be made the responsibility of the applications, but we can also make some changes to improve the developer experience.First of all, we can start to consolidate our config settings into one function so they can be set all at once:
Second, we can still provide default settings that are supposed to apply to applications under a particular domain, but app teams would be responsible for handing that to the config function:
In this way we can have custom configurations at the domain (brand) level and the application level without needing separate entry points with side effects.
Overriding default component props
The other way we rely on side effects is to customize individual components for a brand. These customizations consist of importing particular components and modifying their default props before an application uses them. Here's an example of this happening in the Medicare.gov Design System:
These will need to be handled on a case-by-case basis. The answer for some of these will be to move these overrides into CSS and eventually into design tokens. The answer for others might be to standardize between the design systems. For instance, we've heard that both Medicare.gov and HealthCare.gov want to make a similar change to dialog close buttons, but design work needs to be done and a final decision made before we can move forward. Near the end of this document there will be tables proposing strategies for each individual module that needs to be addressed.
Unique components
The last thing to address are components in the child design systems that are unique to those brands. The most obvious examples of this are the website headers and footers. Components that only have a place in a particular brand can be moved into the core package, but their names will need to be changed to avoid conflicts and to be clear about where they're meant to be used. Here's an example of how they would be referenced:
With tree-shaking in modern build tools, we don't have to worry about extra components being included in the bundles for applications that don't use them. If a team is using the CDN, we will make sure the brand-specific components are bundled separately to avoid unnecessary JavaScript.
Rejected ideas for namespacing
There are several ways we could expose these brand-specific components to our users, and I want to mention them here to explain why they're less ideal. Each example shows two imports to illustrate what the import paths would look like given that you can consume our component library not only as React components but as Preact and Web Components as well.
What is depicted below is healthcare having its own entrypoint that lives in the core package. It is essentially the same thing as having a separate package except that it physically lives in the same package. In order to achieve this, we'd have to manually specify many extra conditional exports in our
package.json
file. While this would avoid most of the work outlined in this RFC, it somewhat defeats the purpose of consolidation.Another option, which is shown below, is to have a single
Header
export with multiple brand-specific components attached to it. This is more concise and arguably cleaner, but it doesn't work with tree-shaking. By attaching the components to an object that you import, it necessarily means that the existence of that property on the object is reliant on that component having been imported. Current build tools to my knowledge are unable to identify that something likeHeader.MedicareGov
isn't being used (in the example below) and therefore doesn't have to be imported.Components that don't have to be unique
There are two components that are unique to Medicare.gov that I would argue don't need to be unique. These are
Card
andStars
. I propose we promote them to core components, but it will require some design work and for guidance to be written.Specific actions to be taken for each module
The following tables list each of the modules that exist in the child design systems and what action we should take to reconcile them with the core package.
Table of actions for HealthCare.gov modules
Accordion
Footer
Header
Logo
locale
andi18n.ts
index.ts
andflags.ts
patterns
#### Table of actions for Medicare.gov modules
Card
Dialog
HelpDrawer
Icons
MedicaregovLogo
SimpleFooter
Stars
Beta Was this translation helpful? Give feedback.
All reactions