portfolio/content/studies/honeycomb/index.md

34 KiB

title layout slug summary draft hero list study
Accessible design system study honeycomb Building a native-first, accessible design system that could actually be adopted across six very different education products. false
title deck html
Accessible design system Building a **native-first, accessible design system** to support six different products, tech stacks, and development timelines. <div id="grid"><div id="shapes"><div id="square"></div><div id="circle"></div><div id="rectangle"></div><div id="triangle"></div></div></div>
more img color facts
My company needed a design system to unify six very different products as one new edtech platform. We did that while managing very different frontends and skill levels, urgent accessibility needs, and no single framework that everyone could share. card.png yellow
key color
Role blue
key color
tools red
key color
industry green
facts toc footnotes
label value icon class
Role <span>Principal designer,</span> frontend architect hat min
label value icon
Timeline Ongoing production system calendar
label value icon
Tools HTML, CSS, JS, Typescript, Figma pen
label value icon
Industry K-12 education SaaS briefcase
label value icon
Scope 6 products, many stacks users
id label
intro Introduction
id label
overview Project overview
id label
constraints Problems
id label num children
highlights Highlights 04.
id label
native Browser-native
id label
layout Unified layout
id label
updates Stable updates
id label
docs Documentation
id label
impact Impact
id label
reflection Reflection
id symbol note
fn1 The DOJ's Title II web and mobile accessibility rule set WCAG 2.1 Level AA as the technical standard for state and local government web content and mobile apps. The original 2026 and 2027 compliance dates were later extended in 2026, but the rule's substance and procurement pressure remained important for education vendors.
id symbol note
fn2 I know that Tailwind generates a lot of emotion. I've been writing CSS by hand for 25 years (and love it) and I'm not ashamed to say that I enjoy Tailwind, though I don't use it all the time. This site for instance is all hand-written, and I've created my own "mini Tailwinds" at times too. It made sense for Honeycomb as it significantly speeds up the time to write CSS/Sass, files are much shorter using `@apply` on single lines, other teams were using it or wanted to, and the config file defining our theme/primitives was really helpful for us.
id symbol note
fn3 § I fully appreciate how ridiculous it sounds to roll our own icon font! I have a lot of experience with them, though, and my manager was incredibly supportive in letting me really enjoy this project too. I think the reasoning stands on its own, but I also do just get a lot of enjoyment making them.

{{< intro id="intro" title="A design system is only valuable if teams can actually use it" >}}

Honeycomb was a large, foundational design-system effort: a unified design language and implemented component library for six acquired products that all looked, felt, and behaved differently. We designed and built 40+ components, plus the supporting patterns, layout scaffolds, utilities, documentation, and connected Figma components needed to make those products feel like they belonged to the same company.

But this case study is not primarily about the mechanics of building a design system. Many systems define tokens, document components, and create a composable UI. The more interesting problem was how we designed Honeycomb from the start to be used inside our particular set of constraints: different products, different codebases, different teams, different levels of frontend skill, and very different modernization timelines.

That shaped both the earliest decisions and the later priorities: browser-native web technologies, a small JavaScript footprint, CSS variables for theming, progressive adoption, and unusually thorough documentation. The system had to meet teams where they were, so the central design and engineering constraint became: make the right thing, but make it easy to adopt before the organization was technically uniform enough to deserve a perfect system.

{{< /intro >}}

{{< overview id="overview" title="Project overview"

}}

Honeycomb was built to support six education products with different histories, product roadmaps, engineering teams, and frontend maturity. We had products that ran the gamut from "we manually copy jquery.min.js and cannot install npm" to "everything is React." The system also existed in two connected places: a Figma component library for design work, and an implemented HTML/CSS package documented through a hand-built site.

{{< figure src="img/overview@2x.png" alt="Button component sample in Figma and documentation" caption="Button component variants in Figma (left) and a documented example (right)." >}}

The package was intentionally native-first. Most components shipped as documented HTML plus compiled CSS, with minimal JavaScript reserved for interactions that could not be handled reliably with browser behavior alone. That meant product teams could drop in the CSS, copy the documented markup into whatever view file or component system they were using, and get a large amount of visual consistency and accessibility handling without committing to React, installing Tailwind, or rebuilding their app shell.

This was also a design strategy. Our products needed to feel like one company without erasing their existing workflows overnight. The system provided shared foundations: header, left navigation, layout scaffolding, form controls, tables, buttons, alerts, theming, icons, and more complex components like datepickers and drawers. From there, each product could adopt progressively.

{{< /overview >}}

{{< problems id="constraints" title="Adoption constraints" >}}

The first major decision was accepting that adoption mattered more than technical purity. While many were pushing React aggressively, our reality was that we could not pick a fashionable component model and tell every product team to catch up.

{{< problem title="Six products had six different technical realities"

}}

The system had to support product teams with very different stacks and skill levels. Some could add one more npm package easily. Some were entirely React-based and would eventually wrap Honeycomb in React components. Others needed a more basic path: copy a CSS file, include a font folder, and use documented HTML in server-rendered views.

That ruled out a React-only system as the first deliverable. It also ruled out a system that assumed every team wanted to learn Tailwind, Sass, or a new build pipeline. The adoption path had to be boring in the best possible way 😏.

{{< /problem >}}

{{< problem title="Adoption had to be progressive with no massive rewrites"

}}

There was no appetite for a synchronized redesign across every product. Each app had its own customer expectations and roadmap pressure, so Honeycomb had to support progressive adoption: a new header now, tables and form controls there, updated buttons and modals in the next feature area, then deeper layout changes when a team was ready.

Components therefore needed stable class names that were prefixed to avoid any collisions, predictable HTML that would ideally never change, and compatibility with existing application markup. Our goal was that if a team adopted a component once, future Honeycomb updates should only change presentation through CSS and never require an underlying HTML update.

{{< /problem >}}

{{< problem title="The system had to cover patterns, not just components"

}}

The most valuable work was often not a button or alert, it was the repeatable and difficult stuff product teams kept rebuilding inconsistently: app shells, responsive layouts, table controls, drawer-based filters, form sections, empty states, and primary/secondary navigation.

By documenting those patterns, the design team handled decisions that would otherwise be made inconsistently across teams. Honeycomb became a shared product language, not just a visual toolkit. Our design team became a shared product support team as well.

{{< /problem >}}

{{< problem title="Federal accessibility requirements created real urgency"

}}

Accessibility was not just a quality goal or a design value—it had become a business requirement. Most SchoolStatus customers are public school districts, and the DOJ's Title II web and mobile accessibility rule created a concrete expectation that public entities would bring web content and mobile apps into WCAG 2.1 Level AA conformance.

For an edtech platform this meant we could lose some of our largest districts (and theoretically almost all of our customers) if we didn't meet these requirements. It also meant that the the product teams could not just treat accessibility as something to "handle whenever we feel like it," and we could not rely on every team independently interpreting the WCAG, writing accessible markup, handling keyboard behavior, and testing every interaction with a screen reader. The company needed accessibility that product teams could pick up off the shelf as part of normal feature work.

Honeycomb had to absorb as much of that responsibility as possible and we took it very seriously.

  • Everyone on the team got accessibility training.
  • Documentation included implementation rules and accessible attributes in every component, and distilled down requirements to plain English.
  • Our team tested everything with screen readers and ran through various disability simulators.
  • We also leveraged browser-native elements that brought accessibility in for free as much as possible.

{{< /problem >}}

{{< /problems >}}

{{< section id="native" title="Browser-native by default" >}}

There are a bunch of projects wins and lessons that I'd love to highlight here, but I'm going to focus on four key ones that cover all the bases.

The first and most important implementation decision we got right was that Honeycomb would not belong to any single product stack or require any kind of upgrade. We decided to be as out-of-the-way as possible and belong primarily to the browser.

Despite the extended discussion, this pretty quickly ruled out a React-first design system early on. Some products would eventually wrap Honeycomb in React components (which was great!) but React could not be the system's lowest common denominator. If the baseline required a modern JavaScript framework, two products would be locked out for too long and the design system would fail at the thing it most needed to do: create consistency across the company before every app was modernized.

So Honeycomb's first-class output was HTML and CSS. Each product team could import a compiled CSS file, copy documented markup, and start using components without changing their build process. That made adoption possible for older apps while still leaving a better path for teams with more modern tooling.

Sass on top of Tailwind

Under the hood, we authored Honeycomb as Sass-based components on top of Tailwind. Tailwind helped us write and maintain the system faster, and the decision was also practical: two products already used Tailwind, so they could compile Honeycomb alongside their own Tailwind configuration and benefit from the same tree-shaking and build process. Other products were encouraged to move in that direction (and one did), but they were not blocked if they could not get there immediately.

{{< figure src="img/browser-tw@2x.png" alt="Sass code sample of the alert Honeycomb component" class="rounded" caption="We took advantage of Tailwind to better leverage our primitives and help write more readable Sass." >}}

JavaScript followed the same model. We exported small vanilla JS modules, and later added a few framework-agnostic web components for interactions that needed more behavior. Teams could include those individually or as a bundle, but static components did not require JavaScript just to look and behave correctly. This included interactive components like dropdowns too!

Native elements whenever possible

We were also strict about using real browser controls wherever possible. A checkbox was an <input type="checkbox">, not a div managed by a JavaScript checkbox library. A button was a <button>. When the browser already provided semantics, keyboard behavior, form behavior, and accessibility expectations, Honeycomb leaned into that instead of replacing it.

In some products, this did mean that we asked teams to remove older UI libraries that had recreated native controls in JavaScript. That was one of the places where adoption did require change, but it was an intentional one: Honeycomb could only provide a reliable accessibility and interaction baseline if the underlying elements were honest.

{{< figure src="img/browser-native@2x.png" alt="Various form controls all using native elements" caption="All form controls looked clean and had subtle animations, still using native HTML elements entirely." >}}

The result was a "tiered" model of sorts: every product could start with compiled CSS and documented HTML, and products with stronger tooling could compile the package themselves. Teams could opt into Tailwind, JavaScript modules, or web components as needed. The system got more powerful at each layer, but the first layer still worked (really well).

{{< /section >}}

{{< section id="layout" title="Unifying six app layouts" >}}

The biggest initial design challenge was trying to come up with an app layout that could plausibly belong to six products that had a lot of unique history. There was little visual overlap and we had at least three different navigation models to work with. Coming up with a modern layout and UI in isolation would've been a dream, but we had to consider legacy interfaces with longtime customers.

{{< figure src="img/unify-old@2x.png" alt="Early screenshots of the acquired apps prior to redesigns" caption="Early screenshots of the acquired apps prior to any redesign or design system integration." >}}

We needed the products to feel meaningfully refreshed and part of the same portfolio, but not so different that each product team would have to significantly retrain users or redesign large workflows before adopting Honeycomb. That pushed us toward a familiar structure: a top app header, a collapsible left nav, a secondary left nav that could sit alongside this when needed, and a main content area that could support everything from dense admin tables to simpler card-based designs.

This seems super obvious in retrospect, but the bigger point is the restraint. The design team (and org as a whole) was excited about the possibility of a more novel layout as we now had the opportunity to introduce something fresh. And we did, in fact, try out a ton of different directions—they just all would have made adoption feel riskier and forced too many product-specific exceptions. The shared layout gave us a stable frame where every product was recognizable, while still creating a clearer, more modern, and professional baseline across the portfolio.

{{< figure src="img/unify-nav@2x.png" alt="Screenshot showing the new app header and left nav" class="rounded-tl" caption="The finalized app header and left navigation, integrated into the teacher training and evaluation product." >}}

Neutral by default, themed by product

The visual direction followed the same logic. Honeycomb components were intentionally restrained: mostly monochromatic, solid borders, plenty of spacing, strong typography, and color used only where it carried meaning. This kept components from competing with each product's identity and helped the system work in very different product contexts.

Product personality came through the specific color palettes instead. Each app had its own palette exposed through CSS variables, so a product could feel distinct without owning a separate component library. These themes could even be applied with simple data attributes to allow for various fledgling integrations where one app feature was embedded in another. This all let us bring six products closer together without flattening them into a single generic interface.

{{< figure src="img/unify-theme@2x.png" alt="Various themed components from the design system" caption="Themed variants of some of the Honeycomb components. Themed buttons used primarily for CTAs and larger actions." >}}

This also helped Honeycomb provide patterns, not just components. A themed button or alert was (very) useful, but another win was that we solved recurring questions each team had about layout patterns, what utilities to include in the header, how navigation should work, etc. Not only did they no longer have to reinvent these things every time in inconsistent ways, we took the decisionmaking chore away as well.

The neutral components also gave those patterns enough consistency to feel shared, while the product themes gave them enough flexibility to work in different app contexts.

App header as an example

The header was one of the most important examples of this balancing act. Each product had account controls and would need to update their product name and logo to the new SchoolStatus-branded one. Beyond that, though, each one used this space for a lot of different things. Two had complex group switching, one had super buttons for users to create different types of content, one used it for their app's entire navigation, and one didn't use it at all.

We used the header to standardize these things cross-product, while also solving some usability issues on both ends of the spectrum. The company plus product logo and the user account controls were the same and now in consistent places. A help button was introduced in those that didn't have it, and put in a predictable place as well. Finally, space was reserved for the two functions the header would be limited to: context switching and high-level app functions.

{{< figure src="img/unify-headers@2x.png" alt="Final versions of the app headers for all 6 portfolio products" caption="Final versions of the app headers for all 6 portfolio products. Certain elements became consistent across products for the first time." >}}

The design stayed intentionally simple so it could work across products without becoming a custom negotiation every time, and we took away the ability to jam other random things into this space. This decision made the header less expressive than some early explorations, but more useful as infrastructure. The header became one of the easiest ways for a product to start feeling like part of the portfolio too, and because it sat above the rest of the app, it could introduce Honeycomb without forcing every screen underneath it to change at once.

{{< /section >}}

{{< section id="updates" title="Built for stable updates" >}}

Choosing HTML and CSS as Honeycomb's baseline solved the adoption problem, but it introduced a different kind of risk: once product teams copied component markup into their apps, that markup became a "public contract"! We could not casually change markup later without creating work across every product.

A JS component system would have given us total control over the rendered DOM, but that path had already been ruled out for adoption reasons. So we treated the HTML itself with the same seriousness a library team would give to an API. The goal was to make the markup simple enough to adopt, stable enough to last, and flexible enough that most future improvements could ship through CSS, variables, and package updates.

That meant thinking through every component's structure up front before it appeared in the documentation. We used prefixed class names to avoid collisions, kept markup as browser-native as possible, and avoided fragile CSS selectors that depended on exact element relationships. Instead of styling something like button > span, we were always explicit with .hc-button .hc-label.

This is mundane but it was one of the most important parts of making Honeycomb usable at scale. In fact, many of the apps were styling things by targeting the raw element and this let us make Honeycomb truly opt-in. The kicker is a particular point of pride: over roughly 2.5 years, we only had to make one breaking HTML change. For a system used across multiple products with nearly 40 components and tons of interaction, this created immense trust in our team and what we were building.

Central changes without product rewrites

Once the HTML for any given component was set, in most cases we could improve the component just by asking the teams to update the library. Anything visual was easy—spacing adjustments, font sizes, color changes, theme updates, etc. could usually ship through the package without asking anyone to rewrite their templates or components. We could even add or adjust interaction at times, and anything that did require a markup change was explicitly to add functionality, never correct.

CSS variables were a major part of that model. Anything theme-related, all primitives, things like our custom shadows and opacities—really anything visual—was accessible and could be adjusted at the system layer easily. One of the biggest wins this introduced was the ability for product teams to access the same style attributes Honeycomb used when they weren't able to use a Honeycomb component.

Icons as an example

We decided to roll our own icon set—not just because I love making icon fonts but because it gave us control over a pretty important component to Honeycomb's personality.§ We looked at open source libraries like Iconoir and Lucide but 3 problems led to us making our own:

  • No single library was complete enough for us
  • I hated the idea of loading massive font files when we only needed a fraction of the icons
  • We needed to make custom icons for things like product logos or education-specific items anyway

We also needed icons to remain centrally maintainable; if every product inlined SVG paths, then changing an icon later would require teams to touch app code in six different places, often on different timelines. So an icon font was born!

We combined open source icons and drew others to fill the gaps, and shipped outline and solid sets as font files within the Honeycomb package. One of the cool aspects is that we made the icon font work as one family with two weights: regular for outline icons and bold for filled icons. You could switch between variants using the same name just by changing the elements font weight.

{{< figure src="img/stable-icons@2x.png" alt="Final versions of the app headers for all 6 portfolio products" caption="Final versions of the app headers for all 6 portfolio products. Certain elements became consistent across products for the first time." >}}

We also added ligatures for every icon as well, so that teams could write readable names like arrow-left as text instead of the normal <i class="hc-icon hc-icon--arow-left></i>. This was partly an implementation convenience but mostly a response to a real product request: one team specifically wanted to place icons using words, as they were doing it that way in their app currently. Supporting this request was just one more way we drove adoption.

Icons are a great example of how Honeycomb needed to be easy for product teams to use, but still governable by the design system. Stable implementation gave product teams confidence, while centrally owned assets let the system keep getting better after adoption.

{{< /section >}}

{{< section id="docs" title="Documentation as support" >}}

The documentation site became one of the most important parts of Honeycomb because it turned the design system from a library into something teams could actually understand and use. Every product team had different frontend experience, and several did not have dedicated frontend developers at all. The docs had to do more than list components—they had to explain how to build with Honeycomb in practical terms.

Each component page included examples, available attributes, expected markup, accessibility notes, and implementation guidance. We documented not only what a component looked like, but when to use it, what options it supported, and what developers still needed to handle correctly in their own product context. That level of detail made the docs a huge time investment, but it also made them one of the strongest adoption tools we had.

{{< figure src="img/docs-datepicker@2x.png" alt="Screenshot of the Honeycomb documentation page for the Datepicker component." caption="Each component was fully documented showcasing attributes, variants, and implementation code for HTML and, as with the Datepicker, web component code." >}}

Writing the docs also improved the system itself. If I had trouble explaining a component clearly, or if an example required too many caveats, or if the classnames were inconsistent with other components, that was usually a sign that the component/markup needed more work. It also let me test the designs in real time, which often improved our components on the Figma/design side.

The docs forced us to be honest about what we were shipping. They were not a layer added after the design system was done, they were part of how the design system got better.

Accessibility in plain English

The docs were especially important for accessibility. The DOJ requirement created real urgency, but "make this WCAG compliant" is not useful guidance for a product developer trying to finish a sprint. We used the docs to translate accessibility requirements into practical implementation rules—when a label was required, what attributes mattered, how keyboard behavior should work, and where Honeycomb handled something versus where the product still had responsibility.

That helped teams get accessibility benefits from Honeycomb without needing every developer to become an accessibility specialist overnight. Components started from a stronger baseline, and the docs explained the remaining responsibilities clearly enough that teams could act on them.

{{< figure src="img/accessibility-note@2x.png" alt="Example accessibility note from the Tooltip component doc" caption="One of the accessibility notes from the Tooltip documentation page." >}}

A playground for real markup

We also built a small HTML playground into the documentation site so developers could test markup live, like a mini CodePen. Since Honeycomb was HTML-first, developers needed to see how attributes, classes, themes, and component states worked together, and they needed a safe place to experiment before copying markup into a product.

The playground also made support conversations easier. If someone had an issue or came to us with an inconsistency, having a live editor running the latest version could quickly rule out what was our fault and what was theirs.

Figma and docs stayed connected

Figma Code Connect added another entry point for developers. In Dev Mode, they could select a Honeycomb component and see the code directly in the design file. This somewhat competed with the documentation site, as a developer might get the code from Figma without checking the docs now.

That was OK because we kept both sources connected through the Honeycomb repo. The same implementation examples that powered the docs also informed the Figma code connections, so the code that developers saw in Dev Mode matched what they would find on the docs site. Figma became the quick path from design to code, while the docs remained the deeper source for examples, attributes, accessibility details, and usage guidance.

{{< figure src="img/docs-figma@2x.png" alt="Screenshots of Figma code connections for HTML and web component code for the Alert component." caption="Each component was connected to Figma Dev Mode via Code Connect, including HTML and Web Component options." >}}

Design became a frontend resource

The result was that the design team became a shared frontend resource for the company. Product teams came to us with Honeycomb questions, but also with layout issues, CSS problems, accessibility concerns, and implementation details that were adjacent to the design system.

This really changed the design team's role quite a bit, particularly at a time when we were just beginning to work with certain product teams. Not only were we creating Figma files and static designs, we were now helping teams build better interfaces in the browser, answering frontend and accessibility questions, and taking recurring UI decisions off their plates.

{{< /section >}}

{{< section id="impact" title="Impact" >}}

Honeycomb has had a profound impact on SchoolStatus as a whole. At the most basic level, it's a shared library of frontend components and patterns that have moved all six products so much closer to a truly unified experience. From accessibility to standardized patterns, it's taken so much off the plates of frontend developers that they're shipping noticeably faster. The design team too is able to create high fidelity designs much quicker and easier.

These are really table stakes for any design system, though, and I want to highlight two major and somewhat unexpected impacts that our specific design system created.

Accessibility became a core competency

Accessibility is often an afterthought at most orgs, and even here where it was considerably more than that, knowledge was still very uneven. One team in particular baked it into their development processes, but most either never considered it or had no consistent way to translate accessibility requirements into everyday product work.

Honeycomb gave us a way to turn accessibility from a "compliance scramble" into a shared competency. The design team led the way in translating requirements into functional code. Components started from accessible markup and interaction patterns, the documentation explained requirements in practical language, and our team became a place product teams could go with real questions: focus behavior, labels, keyboard interactions, screen-reader expectations, color contrast, and when ARIA was actually needed.

The business value was significant too. For a company obviously selling into education, accessibility is not a nice-to-have feature. Accessibility affects trust, procurement, renewals, and risk, and Honeycomb gave SchoolStatus a much stronger answer to that pressure because it was built into the system teams were already adopting.

The design team elevated to a true cross-functional team

Honeycomb also changed how the design team was perceived and used across the company. We were no longer only producing screens in Figma or reviewing implementation after the fact. Because we owned the design system, documentation, and a lot of the frontend guidance around it, product teams started treating us as a practical resource for building the interface itself.

That was especially valuable for teams without dedicated frontend developers. A layout issue, CSS bug, accessibility question, or component adoption problem that might have slowed a team down for hours could often be resolved quickly by someone on the design team. Slack support and the docs site made those conversations easier because we had a welcome place for questions and resources to point people to.

This created a healthier relationship between design and engineering. Designers were closer to production realities, engineers had more support for difficult UI work, and product teams could move faster without re-solving the same interface decisions over and over. Honeycomb made the design team more than a service that handed off mockups, it made us a connective frontend group across the product organization.

{{< /section >}}

{{< section id="reflection" title="Reflection" >}}

The design team's shift to cross-functional team may be the impact I'm proudest of. Not only has it given us a lot of credibility, it eased our relationship-building with brand new teams at a critical point for the company. Design can own the details of browser implementation, accessibility, documentation, and product consistency in a way that makes every team stronger.

Beyond the two unexpected impacts, though, this project has also really driven home for me that technical details do matter, and the "state of the art" isn't always the correct choice. Choosing native HTML over custom JavaScript mattered because it changed who could adopt the system. Choosing CSS-first components mattered because it changed bundle cost and maintenance. Writing exhaustive docs mattered because it shifted implementation work out of meetings and into examples. Connecting code to Figma mattered because it further enabled actual implementation.

The system looks simple from the outside: 1990s-style classes, markup, static docs site, and some web components. Underneath that simplicity, though, is a powerful, standards-compliant system that's prioritized "people are actually using it" over framework fashion or ceremony.

{{< /section >}}