Tony Ward
Articles

Introducing the Glide Core Design System

I'm super excited to announce the pre-1.0 version of Glide Core. Glide Core, or simply Glide as I'll refer to it from now on, is a Web Component Design System using Lit. It serves as a foundational layer for building user interfaces and experiences at CrowdStrike. You can browse our Storybook to see what components we have thus far.

I've been working on Glide since November 2023 at CrowdStrike. We've made a lot of progress and I'm excited to see where we'll be this time next year. There's a lot I could deep dive on, but I thought this article could start with a high-level overview of some of our decisions, values, and technology. I'm hoping to write more around the points listed below in the near future. Stay tuned!

Why Web Components

I've already written about this pretty extensively, but Web Components are great due to interoperability. CrowdStrike is a large organization and supports many different tech stacks. Rather than having to write a Design System for each framework we use, we can leverage Web Components to write components once and use them everywhere.

CSS Custom Properties and Design Tokens

We make heavy use of Design Tokens to allow consumers to set their own theme for components. By default, we ship with our product themes of light and dark modes. These are all driven by CSS custom properties, or CSS variables. These variables are considered part of the public API of each component.

To keep things in sync between code and design, we have a script that pulls all of our variables from Figma and generates CSS variables from them. You can read more about this process. Following this pattern allows us to update all tokens with a script and continue to bridge the gap between design and development. As design updates tokens on their end, we run a script to update the code side of things.

I'm actually in the process of migrating to new semantic tokens for all of our components that align with our reworked Glide Figma UI Kit. You can check out the linked PR and see how our work progresses over time. I'm looking forward to getting this in!

Preventing Misuse

We do a lot to help push folks in the right direction around how our components should or should not be used. Consumers sometimes do crazy things in an attempt to get a component to work a particular way. Sometimes it's due to a design going off the known path. Other times it may be to meet a deadline. Sometimes folks just don't know what they don't know.

Our team has seen this so go wrong so many times. We started coming up with ideas on how to help prevent these types of scenarios. They're costly and create technical debt for everyone involved. The reason we care so much about preventing misuse is because we want to limit the amount of technical debt generated by misuse. It adds up quickly if you don't keep tabs on things!

Rather than folks hacking components together to meet a deadline or to fulfill an incorrect design, our guardrails turn these situations into conversations rather than non-scalable work arounds. Does this require more time? Yes. But it's our job to understand these snowflake use cases, determine if the existing Design System components can fulfill their requirements instead, and either adjust our components or provide guidance on how they can accomplish their goals outside of the Design System.

Why are they wanting a component to act that way? Can the existing component work for their use case? How frequently does this pattern show up through other parts of the products? Do we see this being a new pattern throughout?

We don't want to block product teams from building what they need, but we also don't want them to misuse our existing components and then be surprised-Pikachu-face when things break with an upgrade. We also don't want to put crap in the Design System.

It's a delicate balance. But we think we've found a sweet spot for it all. Here are some ways we prevent misuse.

Closed Shadow Roots

Closed shadow roots seem to be somewhat controversial, but for a Design System I'm a big believer that it makes sense. Our components should be treated as black boxes - they receive inputs via attributes, properties, and methods and render DOM. We want our Input component to be treated like the native <input />. Folks shouldn't need to dip their hands into our cookie jar to get things to work — instead use the public API of the component, just like you do with native. If that's insufficient, then it could be a bug or an improvement on our end. Once again, rather than hacking around things, let's chat!

If you're interested in reading more about my ramblings on this topic, you can here. There are some issues with testing around closed shadow roots, especially when it comes to Playwright, but that's why we open up the shadow roots up in test environments. The key is that we don't want folks mucking with our internals in production code.

Slot Assertions

For components like Menu, we expect Menu Options. Those Menu Options should be either Menu Buttons or Menu Links. Nothing else. These components have design guidelines and UI/UX requirements established by our Design Architects.

Because we only expect a Menu Button or Menu Link as children of Menu Options, we can assert that's the case. If someone puts something random in the Menu Options slot that we don't support, we throw an error in development mode. This goes back to my point above — if someone needs something else, it turns into a conversation rather than random content that may not fit the designed vision.

Some folks ask "why throw instead of log"? Logs get ignored way too frequently. Components throwing during development are much more likely to draw attention to the problem. So that's what we do.

Required Assertions

Lit doesn't expose a way to mark a reactive property as required and enforce it, so we wrote a decorator to do it for us. Use cases for this are mostly around accessibility — we want folks to be forced to provide things like labels. Of course, they can provide garbage, but we always assume the best intent.

No CSS parts

We don't expose any CSS parts (except for a few internal uses) from our components. Instead, we rely on CSS variables as part of our public API. I wrote about this previously as well. We don't want folks to completely override the styling of our components, only what we expose via CSS variables so that the overall UI/UX is maintained. Folks are simply slapping a different coat of paint on things as needed.

Because all of our decisions come from Design, we can get away with this. So far it seems to be working out very well, mostly because we don't need a high level of customization with our components — we support the known cases we have, and that's it.

Disallow Extending

Our components use an @final decorator. This decorator prevents folks from extending our components (using extends). We don't want people extending them because although it may work, it could be a can of worms when it comes to support and upgrading. Instead, we prefer product teams to use our components as-is.

If that doesn't work for some reason, we chat with them one-on-one to learn more about their use cases. If we recognize this requires a snowflake component that is close to another one of ours, we work with them to get the shared functionality they need, decorate the component for tracking, and they're on their way.

Over time, we check in with these different groups that are making these kind-of-like-this-Design-System-component-but-not-quite and assess. Is this component being used frequently across the same or different products? Is it really a snowflake? Should this functionality go in the base Design System component?

This is all in the name of following Josh Clark's ship faster by building design systems slower. Developers these days are too obsessed with creating abstractions for everything way too early. Sometimes letting things grow organically, seeing where they go in a few months, and for the short-term copy/pasting some logic is worth the tradeoff over an unnecessary, unwieldy abstraction.

Uniformity

One of my favorite parts about popping into a repository is a consistent developer experience. I love not being able to tell who wrote which file. I like popping into one component file, reading it top to bottom, popping over to a second component, and seeing similar patterns throughout. This makes following the codebase and contributing to it a lot easier. I really enjoy things being uniform, following the same patterns, and being laid out as similarly as possible.

If you're a designer reading this section, you probably have similar feelings around how Figma files are structured.

Custom ESLint rules go a long way. We use Stylelint, Prettier, Stylistic, existing ESLint rules, and our own ESLint rules. Looking to write your own ESLint plugin? You guessed it — I wrote about this last year and here's the link. We automate as much as we can.

Documentation

Our current documentation uses Storybook and focuses on Component APIs. We have larger plans in motion to build a more full fledged documentation site. Due to following native as closely as possible, our components shoud feel familiar to most folks already. I'm very impressed with Playbook by eBay and other documentation sites and hope we will get there one day.

Testing

We use Web Test Runner at the moment for all of our tests. We like that it tests the components in a browser, like a user would interact with them. We have considered switching over to vitest's browser mode, but we still think it could use some improvements. We also have over 1,000 tests to migrate. We'll continue to review for the next few months, but Web Test Runner is working well for us at the moment.

We have a 100% test coverage bar, which is maybe controversial to some. You can read my thoughts on it here. It seems to be working out well for us!

We're in the process of adding visual regression testing as well. There are some documented limitations with the current tools, but I'm excited to give some of those proposals a try. We will see how things develop! This is being actively worked on at the moment, and I'm sure it's something I'll write about once we land on something that works well for us.

Guiding Principles

I initially had this section at the top of the article, but figured maybe it'd be better towards the bottom. If you've stuck with me thus far, thank you! Let's dive into the principles that help us make decisions around the Design System.

  • Design Architects drive our roadmap.
    • We have Design Architects that have all of the context of products being built with Glide. They understand the product flows and how our Design System building blocks should work on the micro and macro scale. We allow these Architects to drive requirements and our roadmap for building components. We have back and forth on features and functionality, but they own the UI/UX of all components and we build them to their specifications. Each Pull Request with visual or experience updates also goes through a Design Review Process before we merge the components into main.
    • Having folks in this role ensures that our components are being built to help speed up product development, while also ensuring the best experience for our users. It also allows for this group of designers to keep tabs on all of the designs currently being built to help identify patterns and room for improvement.
  • Our users expect consistency in our components when it comes to UI (user interface), UX (user experience), and DX (developer experience) perspectives.
    • We rely on the Design Architects for the UI/UX consistency piece.
    • From an internal developer experience perspective, we enforce consistency with ESLint, Prettier, Styelint, and Stylistic when it comes to being internally consistent. I described this above in uniformity.
    • Externally, our customers/users expect attributes, properties, and methods to be consistent.
      • How we name attributes, properties, and methods should be identical if the ultimate action is the same across multiple components (e.g., a tooltip opening via an open attribute is the same as a Drawer, Modal, Dropdown, or Popover opening, so the attribute should be named the same across all of these components).
  • We follow native as much as possible, where it makes sense, and document when and why we need to break from native behavior.
  • We leverage the platform rather than using third-party libraries when we can. We are okay with progressive enhancements when browser support is lacking.
  • We ensure our components are accessible and respect user motion preferences.
    • We've got quite a bit of animation in comparison to other Design Systems. If folks want reduced motion, we turn them off.
    • We have an accessibility engineer on our team to help with all-things-accessibility.
  • We always "tend to the garden" and make things nicer than we last left them.
    • We're constantly updating the codebase to follow newly established patterns and apply learnings over time.
  • We prefer consistency over flexibility.
    • Rather than being infinitely flexible for all sorts of use cases, our components are laser focused on the known use cases. By being more consistent, it makes decisions a lot easier. It also makes the components easier to reason about. By adding flexibility, it can lead to inconsistent experiences and misuse. Instead, we prefer locking things down as much as possible. We limit flexibility in favor of consistency.

These principles help us make decisions about our Design System. Like all things, we revisit them and decide if they're serving us well, need to be amended, or completely removed. As time goes on, we continue to improve the process.

Give it a Spin!

That's all I've got for now. Go give Glide a spin and let us know what you think. Take a look at our source code. Feel free to ask me questions on Bluesky or LinkedIn. Until next time!


👋