The need for Design Systems comes with the need for scale, efficiency, and consistency in design. Those have been one of the major talking points in the design—and front-end—community for the past years. Some love it; some are more skeptical. In this session, I'm gathering the common denominators I've noticed while working on Design Systems across small, medium, and huge companies, with an eye on points like consistency, themeability, accessibility, the path to deliver designs to actual code, and their adoption.
Consistent UX at Scale: Lessons Learned When I Wore the DesignOps Hat
AI Generated Video Summary
Today's Talk covers the challenges of implementing accessibility in design systems and the importance of using existing libraries. It also emphasizes the use of design tokens and code generation to ensure consistency across different code bases. The Talk explores automation, webhooks, and type safety in design systems, as well as the importance of measuring adoption and building accessibility. Finally, it suggests establishing a DesignOps team to encourage collaboration between designers and developers.
Hello everyone, welcome here. Today we're going to be talking about consistent UX and how to scale that and things, lessons learned that worked across different scales of products and companies. This is me, I'm Mateus Obkerke. You can find me everywhere as YT Combinator, including on Twitter.
Hello everyone, welcome here. I personally find it great to be here, back to all those conferences. I missed it so much, especially React Berlin.
As you probably know, today we're going to be here, talking about consistent UX and how to scale that and things, lessons learned that worked across different scales of products and companies. This is me, I'm Mateus Obkerke. You can find me everywhere as YT Combinator, including on Twitter. I work at Medaglia and I also volunteer at TechLabs Berlin.
We basically are mentoring people, learning programming and that kind of stuff. All the links for this session are available here, so if you scan the QR code, everything is here including networks and stuff. I do have a disclaimer. It's a lot of interesting topics that we have to fit them in half an hour. So, if anything feels like it deserves a proper follow up discussion, just feel free to reach. Throughout the conference, in the after party, everything.
2. Introduction to Design System Challenges
Today, I'm going to be talking about three topics: accessibility, the delivery process of a design system, and understanding the adoption of your design system within your company.
I'd like to start a question. How many of you have worked on a design system? Either as an engineer, as a developer, or designer. Cool! A lot of raisers! So, you know that is definitely something that is not easiest, not a trivial system. Building and scaling that. That's why I'm here today. I'm here to talk about some challenges. Some results of a lot of people working together. The common denominators, as I mentioned, that worked across different companies, etc. It's also a lot about moving across the spectrum of designing and engineering. So, it's a lot about hearing from designers, collecting their feedback about the process, and that kind of stuff. Because we don't have like an hour or hours to discuss, we have to focus on something. So, today, I'm going to be talking about these three topics. Accessibility, the delivery process of a design system, and understanding a little bit about the adoption of your design system within your company. And then we're going to draw some conclusions on top of that.
3. Accessibility and Library Choices
Accessibility is complicated to implement. Press interactions require handling various scenarios and normalizing cross-browser inconsistencies. The implementation of area in browsers can be challenging. Instead of spending time on implementation details, it's recommended to use existing libraries that provide accessibility out of the box. These libraries come in three layers of abstraction: composition primitives, unstyled components, and fully-styled components. The choice depends on the context and the need for customization.
So, starting with accessibility, which is something that we are finally talking a lot about these days. I don't know about you, but I've always had this kind of reaction that we always think, we are, we tend to think that we are addressing accessibility enough. But then suddenly we start getting, I don't know, some tickets or some open issues like, oh hey, this component is not working properly with the zoom, or this doesn't have the proper contrast, or I don't know, the screen reader is having trouble reading that. So, accessibility is complicated for us to implement.
That's the point. And one example is, let's think a little bit for a while about press interactions. So, press interactions can happen on different scenarios. They can happen on mouse clicks, they can also happen on touch taps, they can happen when we press some keys like enter or space keys, and they can happen even on virtual clicks performed by some screen readers. And if we want to properly address them on our own, we have to do a lot of things. So, first we have to use pointer events when they are available, and then fall back to mouse and touch events. We also have to think about text selection, so we have to disable it in some scenarios, especially on mobile when we are pressing things. Not only that, but we have to handle cancelling them sometimes, for example on scroll events. And in the end we see ourselves having to normalize a lot of cross-browser inconsistencies that still exist nowadays, and so on, and so on. Not to mention area.
So, if you've done some accessibility work, you're probably familiar with area. But the thing about Weight Area is that it provides semantics, mostly. So it's like a contract that you should follow. But usually the implementations that we have in the browsers, in the whole web platform, they either lack some feature, or probably you cannot customize them enough, or sometimes it doesn't even exist, so it's up to you as a developer. And that's an incredibly difficult task. So, one of the first lessons we learned was not spend a lot of time, for example, discussing how to implement one or another focus traps, or going through keyboard navigation, or researching the appropriate area thing for something. Instead, finding an existing, and we have a lot of them, library that brings accessibility, mostly, out of the box, and that can be customized enough to match our tokens, our brand, etc. And those libraries, they usually come in three different flavors, or three different layers of abstraction.
So, you have composition primitives, like React Area and Downshift, and as the name suggests, you just build them together, but they don't offer any styling, or anything at all. Sometimes it comes, like React Hooks that you can plug into your components. Then, you have, one level above, there are the unstyled components, those are also known, nowadays, as headless components, so, it's really cool, and you've probably heard about them everywhere. So, we have Redix, ReachUI, HeadlessUI, and Reakit. And then, we have the fully-styled components, so, those usually come with very, very powerful framing systems, and we have a set of components, they are opinionated, but we just customize them to match our design tokens, our brands, etc., via their powerful framing systems. So, the thing is, you should ask, like, which one should I follow, which layer of abstraction or which library amongst these those. It really depends on the context, of course, of your product, your company, etc., but what you should keep in mind is go with one that has accessibility as a core focus and that is also themeable and customized enough to match your stuff. From my experience, I've usually went with composition primitives when, let's say, we have some huge design system implementation running and it's in production and etc., and we're getting a lot of issues with accessibility, we have a lot of code that does styling and that kind of stuff.
4. Accessibility and Library Choices
When reinventing existing systems, consider using composition primitives, unstyled components, or fully styled components based on the specific context. Automated testing for accessibility is crucial, and it can be integrated into the build process or CI. Manual testing is also important, especially for uncovering issues that automated testing may miss. Tools for simulating visual impairments can help identify problems with contrast and color. Consider the specific needs and scenarios of your application when making design and accessibility choices.
So when reinventing existing systems, composition primitives was the thing that worked. For unstyled components, usually when you have the opportunity of starting something from scratch, and I mean from scratch, even the product is being built from scratch, not only the design system. Last but not least, the fully styled components, usually when you don't have full-time designers, I'm going to mention some scenarios, or when the design guidelines match.
So for example, if you are building a product that has material design, it might make sense for you to go with material UI, for example, because it's going to get a lot of things out of the box, plus accessibility. And by no full-time designers, for example, internal experimentation projects at your company. So you're building a dashboard that's going to show some statistics coming from the CI for other developers to see, so you don't have a lot of constraints for the whole design of this app, or you're freelancing on some small project, that kind of stuff. But the fact that those libraries usually bring accessibility out of the box doesn't mean that accessibility shouldn't be tested in your apps.
So, one thing that is really, really important, no matter what solution you deploy, is to include automated testing for accessibility. And, it comes mostly in two different ways. So, you can have it as part of your build process, with tools like JSS accessibility, Lighthouse, Audits, XCore is amazing. But you can also have this in your CI, so that you can even integrate it with your pull requests. For example, the X-Lightner bot is amazing. So, you would have that kind of thing where, for example, let's say I introduced some markup in my JSX code that is breaking some line, it's going to warn me, because sometimes we just don't catch them with the eye. So, having that kind of stuff is really, really helpful. But, even though we are testing with automated testing and stuff, it's also important to include manual testing, because in the end it's where a lot of things happen.
And, I have one example of this accessibility ticket I got a couple of months ago, something like 3 months ago, where basically things were not behaving properly when the application was zoomed to 400%. So, for example, I myself would never have thought of that, because I'm lucky, so I don't have visual impairment issues or any problems with zoom. But, these things happen with manual testing. And this one, for example, was a moment I was like, what? I would have never guessed that. So, manual testing can come when you as a developer use different tools for performing quick checks and those are usually most of the tools I mentioned before do that. But, you also have specific tools for simulating specific visual impairments. So, problems with contrast, like low and high contrast or problems with low vision or some of the many variations that you could have of colorblindness. So, it's really, really important to speak of that. And, speaking of contrast, not only contrast, but a lot of things related to color that do play a really, really important role in accessibility. So, if we do one exercise, for example, and we try to build a color palette with accessibility with contrast in mind so, let's say we have a light and a dark theme and then we have our 0 to 9 scales and ect. So we start with the color, picking a color for the length in the app. So, we go with blue and then we go with the opposite in the dark palette that is the number 4 shade, so, okay, it works. But then, for icons, we go with the number 6 in the light one and then number 3 in the dark one. But then, after running some contrast checking or even sometimes after going through users you might realize that actually what you want is the exact opposite as it's natural for us to go with. And then, you can have even more different scenarios.
5. Color Selection and Design Tokens
When picking accent colors, expect that some colors might require bigger adjustments in the palette. Cooperation between designers and engineers is crucial for optimizing work. Delivery of a design system is a tricky question, and the source of truth can be the existing code base, existing UI, or documentation and design specs. Working with Figma and its REST API provides access to metadata about designs and plays well with the concept of design tokens. Design tokens are invisible pieces of design like colors, spacing, typography, or scale. They can be seen in Figma's color styles, font styles, components, and variants. The REST API allows for code generation based on fetched elements and their properties.
So let's say you're picking an accent color for some buttons, some call to action, that kind of stuff you start with the opposite, but then you realize that actually the color you want is way above on the scale. So, another lesson I would say is some of those colors you might need bigger adjustments in the palette and they might move a lot. So, expect for that. And, I always like to mention, for example this work done by the Stripe folks, which is amazing so they did a whole case study of colors plus accessibility and they built this tool where they were experimenting for example with different kinds of color blindness and etc. And, the conclusion they built are amazing and that's the result of designers and engineers working together So, I would say that for us, mostly frontend engineers here, I guess, it's really, really important that we cooperate with the designers and that we build tools to optimize their work. Especially, because in the end we're going to be optimizing ours as well. Whoo!
So, yeah, let's talk about delivery Because in the end you plan a lot and you design your design system and etc. But it should reach users, right? And it should reach the engineers using it So, it's usually a tricky question how to identify the source of truth of a design system Because it might be the existing code base, so you have the components and you should go from there. It might be the existing UI, so you have a lot of existing elements in the web, in the apps running in the web, etc. And you have to go from there. Or it might be documentation and design specs and that kind of stuff coming from the engineers So, one of the topics I want to address here is Chipping design to code and I would like to ask a question. So, who here is working with Figma? Yeah, I'm so glad this is going to work. Yeah. So, who here has worked with REST API on Figma? I'm so glad again because all of you knew that so it wouldn't be a point. So, for those who did not raise hands in the last question Figma does have a REST API which is amazing because using that API you can get a lot of information, metadata, about your Figma designs. So, you have all the elements, all the nodes, and everything about them. And this plays really well with the concept of design tokens. So, according to the web standards community, design tokens are those invisible pieces of design like colors, spacing, typography, or scale. And we can see them in Figma, for example, when we have our color styles and the font styles. And we can also see in our components and the variants of a given component. And it's really powerful what we can do with that. So, here in this example, I have... I'm generating both dark and light palettes for the React labeling brand. So, you can see all the shades of blue and et cetera. And with that, I can in the REST API, I can write some code generation code for code generation. And yeah, I know it's a lot of lines to read so you're going to have the slides afterwards, no worries. But the point is what's happening here is basically, I have some code for fetching, communicating with the API, et cetera. Then, I have some code to transform the nodes I'm getting from the API. So, accessing the elements and getting their color or the name I set for that one element so that I have all the information. And then, last but not least, I have some code generation.
6. Code Generation and Design Tokens
I'm creating TypeScript files and Sass files with CSS variables to ensure a consistent source of truth across different code bases. By using code generation logic, we can scale this approach to work with fonts, icons, and more. Tools like SVGO and Prettier help optimize and ensure code conformity. Additionally, there are GitHub tools like Figment and Figma Export icons that simplify the consumption of Figma's resources. The Figma Tokens plugin is a powerful tool for generating various design tokens, including colors, fonts, spacing, and more.
So, I'm creating TypeScript, React files... I'm sorry, I'm creating TypeScript files and I'm creating Sass files and CSS variables because that's really important when you have different code bases built with different technologies and you want to have the same source of truth. So, we had, for example, Angular code bases that used Sass for styling and we had React code bases that used some CSS and JS strategies, so we wanted the tokens to be in TypeScript, for example. And then, you do some engaging GX on top of that. You build, like, custom logging and some nice scripts to show what's happening that you can run locally, you can run in a CI. And then, the result is that kind of thing. So, you have all the colors and the variance and the whole theme defined in both TypeScript and Sass for us. But you can add any kind of compilation target, to be honest, because you are writing the code generation logic. And this is great because it scales. So, it works with fonts as well. So, not only colors, but it works with pretty much anything, including icons. And you can pipe in a lot of different tools in the whole process. So, for example, we were co-generating SVG icons coming from the designing files. So, you can use SVGO to optimize the SVG output, so that you have, like, the most semantic SVG and that kind of stuff. You can also use Prettier to ensure that the code that's being generated, like the Sass files, the TS files, and even, for example, we were co-generating React components for icons. So, you can ensure via Prettier that everything that's co-generated is conforming to your rules and your codebase and etc. And you even have other tools on GitHub that allows you to not write a lot of logic to consume Figma's stuff. For example, you have Figment and Figma Export icons. For, let's say, you're just interested in the icons. So someone has done the job for you. And you literally just paste your API keys and stuff like that. And not to mention that again, we should be building tools to help designers and developers and etc. So, people are doing this. We have, for example, the amazing Figma Tokens plugin that generates for you a lot of different design tokens. Not only the ones I mentioned like colors and fonts but complexes, spacing stuff, line heights and a lot of possibilities. And this is the kind of output you get, for example, if you are using it for your color tokens. And you have something analogous for all the other things. So we were doing a lot of code generation etc. in the past. But we had kind of a gotcha moment when we found out that that one script I showed you, we were running that locally and etc.
7. Automation, Webhooks, and Type Safety
We experimented with automation and code generation to streamline the design process. Initial iterations involved CI checks and versioning based on div files. Later, we implemented webhooks for reactive updates and allowed designers to change variables. We then added more automation, such as Slack notifications for failing builds and Figma plugin integration. We eventually moved to Figma tokens for better mapping and type safety. Type safety is crucial in design systems, as it helps with productivity and avoids referencing nonexistent class names.
Because it was experimentation and we were doing a lot of proofs of concepts and etc. So we wanted to automate things but designers would still have to Hey, I've just pushed an update to the color palette or something like that. Can you regenerate again? And publish the new version to npm and that kind of stuff. So we started doing different iterations of code generation and stuff like that and automation.
In the first of them we had the CI to automatically check for updates and that kind of stuff. And then we also had versioning based on div files that we were generating. So you would compare the JSON outputs and all that kind of stuff and we would have the div files and by the way, when I say CI to automatically check, it is basically scheduled runs of a CI for example, always in the end of the day or sometimes weekly runs. It depended. And then we had the change log and the versioning of the package for example based on those div files. So, we would have, like, a lot of magic to discover okay, is this a minor? Is this a patch? Is this a major? That kind of stuff. Never majors, by the way.
And then on the next iteration, it was huge because basically we started having webhooks. So, these scheduled runs in the CI became reactive via webhooks. So actually whenever the designer published some changes to the Figma file, we would be notified and we can run things. And then we got to the point where we have designers able to change hues, colors, font variables, all the sort of things based on that. And then, automation always calls for more automation. So, basically on the next step, what we wanted to have was, for example, notifications for failing builds or new releases, that kind of stuff on SlackChannel for example, which is kind of trivial to do. Also the Figma files created by the designers, so that it wouldn't break our code generation process via a Figma plugin. Again, building tools to help designers, and help our service. And then moving to Figma tokens because we didn't start with Figma tokens, so we had a lot of code to map Figma stuff into our stuff.
And for those not familiar with the webhooks API with Figma, it looks like this. So you have a lot of different events that you can listen to, files creations files deletion, file updates, that kind of stuff. And the Gates gives you a lot of metadata. Another thing is type safety. So, that's a really important thing in a design system. And we usually don't think about it. But, I myself started working on this SAS based code base I found myself with two issues. First, I was really doing slower than I was before, for example when doing CSS and JX or anything else that was typed. I missed auto completion, I missed the compiler telling me, hey, you mistyped the name of this class or something like this. Plus, I found, especially in a huge code base, a lot of class names that did not exist at all So, you had a component referencing a given class name but this just didn't exist.
8. Fixing UI Bugs and Broken Builds
At scale, this problem can generate unexpected behaviors in the UI. We fixed it by using Webpack and finding plugins to generate types. After fixing broken builds and running tests, we regained item completion and caught numerous bugs.
So, that was a problem, not only for us, but for users. Because at scale this can generate unexpected behaviors in the UI So, the effort to fix that was basically we were using Webpack and etc, especially because it was a huge legacy system and Webpack was cool back then and yeah, it was nice. And so, we had to find plugins and to pipe that to our generation process and luckily there are plugins for generating types for Webpack and then we had to fix the broken builds and run some tests to test whether it was working as expected or not and the results were basically I had my item completion back, which was great and we caught a lot of bugs so, for example, the code review of this one was amazing because literally we saw as the builds were failing and etc, that we had a lot and it was in the end 96, by the way, cases of unexistent classes being referenced
9. Usage and Adoption of Design Systems
With template literal types, we can model directions and build CSS padding. Adoption is crucial for design systems, but developers often rebuild existing components. Usage can be measured using homebrew components and static analysis tools like RADIUS Tracker. It's important to know if your design system is being adopted and to build accessibility as you go. Perfect is the enemy of good, so get results early and enforce best practices with automation tools. Design systems are about more than just classes and token definitions; they require tooling, support, and advocacy.
With template literal types instead we can model what a direction is, like left, right, bottom, top, etc and then build CSS padding on top of that. And this is chaos because we can do margin on top of that as well and so on and so on
And the last topic is adoption. So, in the end, a design system has to be actively used to improve dx and ux and etc because otherwise it's just a waste of effort. But the thing is, we developers, we tend a lot to rebuild things even though they exist. So we tend to create our own components even though the design system we have is already providing them and etc.
So, how do we even think about usage? How do we define usage of a design system? For this, we have a really interesting concept that's called homebrew components. So, basically, homebrew components are the ones built around primitives, so around buttons, divs, etc. And, non-homebrew components is basically when you compose other homebrew components to have a component. And they are important because if you want to see how much of usage a design system has, then you have to find the existing homebrew components, then you have to find the components coming from the design system, and then you have to collect usages of both and etc. You have to calculate proportion, and then last but not least, you have to do that throughout the history of the project to see if it's going up or down. But the thing is, calculating usage share is really, really tricky, because let's say we have a component but we can have different variations. We can, for example, rename this component or we can, for example, pass it as a prop or we can, for example, enhance it with a high-order component or we can rename it when importing it. So there's a lot of different things that might happen and this leads us to realize that usage is a fused concept. So it's complicated to calculate and etc. You have to find a tool that does this measure work for you, a static analysis tool, and I found this one called RADIUS Tracker by Rangel and there's a really, really interesting case study that they did with Grafana UI, that is an open source design kit where they track the absolute usage numbers throughout time and they get really, really interesting things like the design system share. So you can see the share over time, etcetera, and that's really amazing because what you're looking is you want to have a share that's growing, not decreasing.
So it's important to know if your design system is being adopted and the moment you start doing this you will see that evangelism is really hard and especially internally in your company. The second thing is you should build accessibility as you go because in the end if you think of it at component level you see that you'll get it for free. You won't have to do a lot of refactors and things like this. Third point is perfect is the enemy of good and that's a cliche but that's true so the earlier you have results the earlier you can get feedback and etcetera. Enforce all of those things via automation tools and etcetera and document what you're enforcing. And we tend to think that design systems are a lot about classes, token definitions, typefaces, and etcetera. But it's a lot about tooling, active support, and advocacy. Last but not least I love this quote that is basically, in god we trust all others must bring data. So these are things that work for my context. Yeah, always think and always experiment and always try to get your data. And that's all. Thank you. Thank you ever, ever so much.
10. Q&A and Collaboration
In the interest of time, the Q&A session will be skipped. ARIA support is often forgotten in in-house design systems, but it can be enforced through tools like linters and bots. Using headless component libraries in existing design systems can be problem-free, as they provide hooks and utility functions without introducing styling. To encourage collaboration between designers and developers, consider establishing a DesignOps team within your company.
Thank you. Thank you ever, ever so much. In the interest of time, I think we're going to have to skip the Q&A. Five minutes, two minutes of Q&A. Two minutes of Q&A. Come on join me over for some Q&A. Excellent, and there were lots of absolutely fantastic questions but just a reminder that we certainly won't get to them all. But after this session you can have a chat with Matthias in the speaker Q&A room and I'll leave the questions not asked in Slido for a little bit as a basis for discussions that happen in that room.
Hello. Hey. Thank you so much. I'm going to jump straight in. How can we help engineers remember about ARIA support when using an in-house built design system? Usually ARIA support is optional and is forgotten while developing. So I'm kind of biased when it comes to this because what I've seen recently, especially in the company I work with is we're constantly remembered of ARIA support by our customers because they have to adopt some regulations something like this, for example customers in the US or big things like Google, Apple etc. They have to conform with ARIA stuff, especially because you have laws. So for us most recently what's happening a lot is we get tickets hey we had this problem and before you release this new version of your product you have to kind of fix it. So for us it's kind of a reactive effort. But if that's not a reactive effort in your company, then ensure it via tools have linters, have bots like the ones I showed checking ARs and that kind of stuff. That would be my advice.
Excellent. Next question. What are the problems with using headless component libraries in existing design systems? Actually there's no such problem. When I mentioned it was mostly from my experience that, I don't know it just happened maybe because of the size of the code base we were trying to migrate and having even though headless components they don't bring in any styling but they're a bit optimated in component level and we didn't even need that. We literally wanted some hooks and some utility functions that we could plug in in our existing components. But it's not like there's a problem. It's just that the primitives just happened to work better. Yes, that was the sensible approach.
I think we'll do one more, which is are there any low hanging fruit to get designers to collaborate on GitHub or vice versa, devs joining design tooling to stop drift of Figma and product? That's a really interesting one. That's usually what you should the title of the talk was when I wore the DesignOps hat Lately we've had this let's say of DesignOps, that usually people doing that are either designers that tend to go towards engineering or the other way around Engineers that tend to go towards design. So I would say that try to see if your company, especially large companies, they already have someone in DesignOps But if they don't and you're willing to push that start trying to collaborate with the designers and maybe even finding something that's gonna start with two people and then three people a DesignOps team. GitHub has a really interesting case of building a DesignOps team from scratch a couple years ago Yeah, I might try to find that. There's an interesting talk about that I might try to link that. But yeah, usually, if you don't have a DesignOps person or team and you're willing to push that. It's not ideal but you should be that person.