Easily restore your project to a previous version with our new Instant One-click Backup Recovery

How Hygraph uses Hygraph IV: Frontend implementation

How we implemented the new Hygraph website.
Özgür Uysal

Özgür Uysal

Sep 19, 2024
Mobile image

Throughout my career, I’ve consistently worked on either rewrites or brand-new projects—always by coincidence, never by choice. When I joined the Hygraph marketing team as a Frontend Developer about 2.5 years ago, I thought things might be different. However, after a rebranding project, the inevitable news arrived: we were going to rewrite our marketing website. Just as a side note, this still hasn’t changed. Currently, I’m leading the Baukasten Design System for Hygraph Studio. And yes, you guessed it - it's another rewrite.

In the first part of this article series, Dino Kukic, our Head of Demand Generation, explained the reasons why a rewrite, but to recap and expand on that, our old website was initially built with Gatsby, then at some point it was ported to Next.js and partially TypeScript. The codebase was a bit difficult to manage and there were a lot of inconsistencies with design. Adding new features or even pages was getting more and more difficult. Our schema was designed with a very early version of Hygraph without components and many new great features added throughout the years.

So, we decided to go with a rewrite, to build a more future proof website, utilizing the full potential of Hygraph. With my background in design and past experience, I felt it was the perfect time to build a Design System. This would allow us to design and develop primitive components like buttons, tags, tooltips, dialogs, and form fields, followed by larger reusable blocks that make up the web pages - such as hero sections, logo clouds, testimonials, callouts, and card grids.

This approach would give us a more organized, maintainable structure while boosting productivity when building new features and pages. A modular setup like this would also integrate seamlessly with Hygraph's components. To validate the idea, I built a small proof of concept—a basic design system and website project with a few primitive and block components powered by Hygraph, demonstrating how easily new pages could be created. After presenting it to our VP of marketing and CEO, we got the green light!

So we started with a new design system project. If you haven’t yet read about it, you can read more in this blog post. In this article, I will explain things from a bit more technical aspect.

#Working with the design system

Foundational goals

Our foundational goals were maintainability, scalability, accessibility, and consistency. Anything serious you build, should be based upon a good foundation. Our design system was one of the key foundational elements, so we approached its planning with great care.

Building a design system involves a lot of iterative thinking and brainstorming, requiring close collaboration between designers and engineers. It’s a challenging task—not only are you solving current problems, but you also need to structure it in a way that's adaptable for future needs. Our ultimate goal was to create a modular website built with block components composed of primitives, making the process of designing, building, and publishing new pages seamless.

Styling and theming

There are many different tastes and preferences when it comes to styling. Some prefer writing vanilla CSS while others may want to go with a CSS in JS solution. These are all great options. I think it’s best to pick a solution the team is the most comfortable with. We picked Tailwind CSS as our styling solution.

There are some obvious reasons why we chose Tailwind, such as the ease of maintainability, utility-first approach and how simple it is to add responsive styles. However, one of the main factors that made Tailwind stand out was its extensive customizability. We were able to customize colors, spacing and typography to align perfectly with our design system requirements.

We started with a core palette. This step is explained in more detail in the previous blog post. Then we built our semantic tokens based on the core palette.

undefined

In essence, the core palette is the pool of colors you have in your design system. You can use it to build other palettes, typically the semantic palette and the illustration palette on a website project.

undefined

You might be wondering, why create a semantic palette and semantic tokens when we already have color tokens defined in the core palette. Simply because core tokens do not convey any meaning. You wouldn’t immediately know the use case for a color token like 'indigo-600,' but when you use a token named 'bg-primary,' it becomes clear that it's meant for surfaces where the primary color should be used.

Semantic tokens provide context and meaning, making it easier for designers and developers to understand when and where to apply specific colors. For example, you can define tokens for 'secondary' colors, or for interaction states like 'hover' and 'pressed.' This approach improves clarity and consistency, allowing the team to focus on intent rather than trying to remember specific color codes.

With our semantic palette in place, integrating these semantic class names into Tailwind CSS was straightforward. While building our design system tokens, we used a very useful plugin called Tokens Studio. It exports all the token values as a JSON file which you can even connect and sync to a GitHub repository. You can use this file to transform your tokens into any format you like. At this point, another amazing library called Style Dictionary would be a great help.

Tokens.json

{
"colors": {
"indigo": {
"600": {
"value": "#5b4cff",
"type": "color"
}
},
"grey": {
"600": {
"value": "#596a95",
"type": "color"
}
}
}
}

Using the JSON output, we first created our CSS custom properties (a.k.a CSS tokens or CSS variables) to be referenced in our custom Tailwind preset. So our tokens.css file looks like this:

tokens.css

:root {
--bg-primary: 91 76 255;
--state-primary-hover: 91 76 255 / 0.15;
--state-primary-pressed: 91 76 255 / 0.25;
...
}

Now we could reference these values in our Tailwind preset.

tailwind-preset.js

export const tailwindPreset = {
theme: {
backgroundColors: {
primary: "rgba(var(--bg-primary))",
"state-primary-hover": "rgba(var(--state-primary-hover))",
"state-primary-pressed": "rgba(var(--state-primary-pressed))",
...
},
},
};

This allowed us to enforce only the colors, typography and spacing class names available in our design system by avoiding any other built in Tailwind class names. So a developer wouldn’t be able to add a color like bg-red-300 out of nowhere.

Another big advantage of using CSS custom properties is the ease of theming. Tailwind CSS lets you add a dark theme using the dark: modifier. This works nicely but you have to go through all the files and add your style overrides and that’s a very tedious task. If you want to add another color theme, you’ll have to follow a similar process and not only your class names in your HTML will be bloated but also it will be a nightmare to maintain.

Instead, you can just create a new set of your CSS variables per theme. Here’s an example. Typically, the background color of your page will be white in a light theme, dark gray in a dark theme, and black in a high-contrast theme. For these scenarios, your CSS variables might be defined as follows:

:root {
/* Light mode: white */
--bg-base: 255 255 255;
/* Dark mode: dark gray */
.dark {
--bg-base: 10 10 10;
}
/* High contrast mode: black */
.high-contrast {
--bg-base: 0 0 0;
}
}

Tailwind CSS would generate the bg-base class name similar to this:

.bg-base {
--tw-bg-opacity: 1;
background-color: rgba(var(--bg-base) / var(--tw-bg-opacity));
}

As you can see background-color property points to a CSS variable. Finally you can switch to dark and high contrast themes by assigning .dark and .high-contrast class names respectively to the root element in your page. For example, when you switch to dark theme, the HTML would look like this:

<html class=”dark”>
<body class=”bg-base”>
...
</body>
</html>

Needless to say, switching between themes requires some JavaScript code to assign the necessary class to the html element. Using this technique, you can add practically any number of color themes.

#Components

We needed over 40 primitive components, such as buttons, dialogs, and checkboxes—these are the smallest building blocks for any app. By using these, we could then assemble block components, bringing us one step closer to achieving our goal of a modular website.

Given the resources and time constraints, we wanted to use a battle tested underlying React library, which also gives us the accessibility features we needed. React Aria was a clear choice for us. It’s a great library of unstyled React components and hooks, built and maintained by a very professional team at Adobe, Inc. It provided us with great customization options, unlike any other headless component libraries.

React Aria takes all the heavy lifting by assigning proper aria attributes, managing the state and interactions and lets you structure and style your components the way you want. We were able to build some completely custom components using the hooks. If you do not feel comfortable using hooks, React Aria now also has React Aria Components, their headless component library built on top of React Aria.

#Blocks

Blocks are larger components of our website that enable quick page construction during both the design and implementation phases. They are crucial when content editors need to build and publish pages independently, without involving a designer or developer. These blocks are essentially made up of primitive components and include elements like Hero, Callout, LogoCloud, and Navbar. For instance, a Hero component might have an API that looks like this:

<Hero
title="Unlock your content's full potential"
description="The headless CMS powering content for mission-critical applications."
ctaButtons={<Button>Try Hygraph for free</Button>}
media={<img alt="Hero image description" src="/img/hero.png"/>}
/>

You can add various optional props for changing the orientation, adding a label or triggering a video dialog to have multiple variations of the same component. You should really adopt a design thinking approach in your team to achieve a modular website.

undefined

#Conclusion

Building a new website on top of a design system is both challenging and rewarding. Our foundational goals helped us create a system that promotes consistency, accessibility, scalability, and modularity. By using Tailwind CSS for styling and theming, and React Aria Components for accessible components, we were able to keep our workflow efficient and clean.

I hope these insights are helpful for your next project. We’ve kept things brief, but many of the topics we touched on in this series could easily be explored in more depth. If you'd like to dive deeper into any specific area, feel free to reach out—we'd love to hear from you.

Blog Author

Özgür Uysal

Özgür Uysal

Team Lead Engineering