The Process of Building a CSS Framework
We recently released version 3 of CodyFrame, a CSS framework Sebastiano (my partner at CodyHouse) and I have been working on for the last three years.
In this article, I want to show you the steps that brought us to this last version, the key features of the framework, and how CodyFrame and the library of components that come with it – almost 400 – can be used to create web pages in no time.
Here’s a preview of what we’ll build:
Live demo:
https://ambercreative.co/codrops/
Click here to skip the ‘framework story’ and go straight to the tutorial.
Why we built CodyFrame and how it evolved
7 years ago, we started CodyHouse as a collection of web design experiments. We did so to promote our web design agency. There weren’t many websites back then that were sharing similar content (Codrops was one of our main inspirations).
Our library grew a lot in popularity, but we soon realised how difficult it was to include multiple experiments in the same project: they had disconnected CSS code, which required extracting the common style (e.g., buttons, typography) to avoid code repetition and style inconsistency.
This is why we decided to build CodyFrame and a new library of connected components.
The original idea was to create a super light CSS framework that would include only a set of global styles (e.g., colors, form elements, and buttons) that we could reuse each time a new component was created.
As the library kept growing, we started facing a few problems: by including layout decisions into the components (e.g., margins and paddings), it was difficult to create layout variations.
Our components are built using the BEM naming convention, so even the smallest change required a component variation:
<div class="component component--padding-y-sm">
<!-- ... -->
</div>
<div class="component component--margin-y-xl">
<!-- ... -->
</div>
<style>
.component {
padding: var(--space-md) 0;
margin: var(--space-md) 0;
}
.component--padding-y-sm {
padding: var(--space-sm) 0;
}
.component--margin-y-xl {
margin: var(--space-xl) 0;
}
</style>
We decided to include a few utility classes into the framework, and it turned out to be super useful not only to dry the components code but also to make them easier to customise.
<div class="component margin-y-md padding-y-sm">
<!-- ... -->
</div>
<div class="component margin-y-xl padding-y-md">
<!-- ... -->
</div>
We’ve since then included a lot of utility classes and developed a system to build a scalable CSS architecture based on BEM and utility classes.
CodyFrame v2
In v1, each global file contained both the base rules and the custom style:
.btn {
/* base style */
display: inline-flex;
text-decoration: none;
/* custom style */
background-color: var(--color-primary);
color: var(--color-white);
}
One flaw of this version was the upgrade process: when a new version of the framework was released, the changes to the base code would be mixed up with the custom code the user had already modified.
To fix this issue, in CodyFrame v2 we decided to split the framework into two parts:
- Basic style – essential CSS rules and utility classes;
- Custom style – SCSS templates to create your bespoke style.
css/
├── base/
│ ├── _accessibility.scss
│ ├── _buttons.scss
│ ├── _colors.scss
│ ├── _forms.scss
│ ├── _icons.scss
│ ├── _spacing.scss
│ ├── _typography.scss
│ └── _util.scss
└── custom-style/
├── _buttons.scss
├── _colors.scss
├── _forms.scss
├── _icons.scss
├── _spacing.scss
└── _typography.scss
Each time a new version of the framework is released, our users can replace the ‘base’ folder with the new one without affecting their bespoke style!
CSS Custom Properties
Since the beginning, we have been using CSS custom properties to handle variables in CodyFrame.
CSS variables are great for code maintenance: you define the variable in one place and use it everywhere in your code. If you need to modify its value later, you only need to update one line of code, and it will propagate through the entire codebase.
But we picked them (mostly) for a different reason: unlike SASS variables, you can update the value of a CSS variable using a class or media queries.
.icon {
--size: 1em;
height: var(--size);
width: var(--size);
}
.icon--sm {
--size: 16px;
}
This difference opens a world of possibilities. We’ve built a system where spacing and typography are controlled by a few CSS variables, with almost no need for media queries.
Why not using an existing framework
Building a framework is a learning process. It taught us a lot about code maintainability, and it freed us from chasing other frameworks updates.
Sure, it takes time to convince developers we’re worth a try. But, in the long term, it’s a win-win deal for both us and our members.
CodyFrame v2 turned out to be very stable: it has been out for more than 2 years, with more than 40 minor releases.
CodyFrame v3 was launched a few days ago to include the new SASS Modules with some important tweaks to the color and spacing systems.
Our framework is now downloaded almost 10’000 times/month, and I consider this a great achievement.
Plans for the future
We just released CodyFrame v3, and we are still keeping an eye out for issue reports.
We plan to keep shipping new components and templates until we run out of ideas. We’re also looking into native web components, that would make our components compatible with all the popular JS frameworks.
CodyFrame in action
Now the fun part! Let’s build something with CodyFrame and the components!
Here’s the final result:
https://ambercreative.co/codrops/
To keep the tutorial short and sweet, we’ll include CodyFrame via CDN:
<!doctype html>
<html lang="en">
<head>
<script>document.getElementsByTagName("html")[0].className += " js";</script>
<link rel="stylesheet" href="https://unpkg.com/codyhouse-framework/main/assets/css/style.min.css">
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<!-- ... -->
<script src="https://unpkg.com/codyhouse-framework/main/assets/js/util.js"></script>
<script src="assets/script.js"></script>
</body>
</html>
If you want to create your custom style (e.g., modify buttons, typography, …), you can download the SCSS templates included in the framework and modify them using our global editors, as explained in our Documentation.
As the main navigation of the page, we’ll use the Main Header components from our UI library.
Copy the HTML, CSS and JS code using the buttons in the top-right corner of the page and include them in your project.
We can customise the HTML structure of this header: let’s delete the code for the ‘Download’ button item and the divider (.header__item--divider
) and let’s remove the aria-current="page"
attribute from the ‘Resources’ link – that attribute is used to style the current page link. In our case, it is the home page, so we won’t need to set it for any of the header links:
<header class="header position-relative js-header">
<div class="header__container container max-width-lg">
<div class="header__logo">
<a href="#0">
<svg width="104" height="30" viewBox="0 0 104 30">
<title>Go to homepage</title>
<!-- ... -->
</svg>
</a>
</div>
<button class="btn btn--subtle header__trigger js-header__trigger" aria-label="Toggle menu" aria-expanded="false" aria-controls="header-nav">
<i class="header__trigger-icon" aria-hidden="true"></i>
<span>Menu</span>
</button>
<nav class="header__nav js-header__nav" id="header-nav" role="navigation" aria-label="Main">
<div class="header__nav-inner">
<div class="header__label">Main menu</div>
<ul class="header__list">
<li class="header__item">
<a href="#0" class="header__link">About</a>
</li>
<!-- more list items here -->
</ul>
</div>
</nav>
</div>
</header>
For more info on the Main Header component, you can check its Info page.
For the hero section, we’ll be using some of the CodyFrame utility classes.
<section class="container max-width-adaptive-md padding-top-xl padding-bottom-xxl">
<h1>Lorem ipsum dolor sit amet consectetur adipisicing elit</h1>
<p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>
<div class="flex items-center gap-sm">
<a href="#0" class="btn btn--primary">Download</a>
<a href="#0" class="color-inherit">Learn more</a>
</div>
</section>
The .container
and .max-width-adaptive-md
classes are used to set the width of the hero element. The flex classes (.flex
/.items-center
/.gap-sm
) are used to align the action buttons and create a gap.
To complete the hero section, we’ll need to 1) animate the headline with rotating words, 2) create the offset effect of the paragraph element, and 3) create the hover effect for the ‘Learn more’ link.
For the offset effect, we can use the grid utility classes: 1) the .col
class (and its responsive modifier .col-8@md
) to change the element width at a specific breakpoint, and 2) the .offset
class to offset it.
<div class="grid">
<h1>Lorem ipsum dolor sit amet consectetur adipisicing elit</h1>
<div class="col-8@md offset-4@md">
<p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>
<div class="flex items-center gap-sm">
<a href="#0" class="btn btn--primary">Download</a>
<a href="#0" class="color-inherit">Learn more</a>
</div>
</div>
</div>
To animate the headline words, we can use the Animated Headline component (the ‘clip’ variation).
Let’s copy the CSS and JS code of the component in our project. Then use the HTML to replace the <h1>
element of our hero section:
<div class="grid">
<h1 class="text-xxxl text-anim text-anim--clip js-text-anim">
We <span class="text-anim__wrapper js-text-anim__wrapper"><i class="text-anim__word text-anim__word--in js-text-anim__word">design</i><i class="text-anim__word js-text-anim__word">develop</i><i class="text-anim__word js-text-anim__word">create</i></span> digital experiences
</h1>
<div class="col-8@md offset-4@md">
<p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>
<div class="flex items-center gap-sm">
<a href="#0" class="btn btn--primary">Download</a>
<a href="#0" class="color-inherit">Learn more</a>
</div>
</div>
</div>
Finally, for the link hover effect, let’s use the Link Effects component. Import the CSS code, then copy the HTML and use it to replace the original <a>
element:
<a class="link-fx-1 color-contrast-higher" href="#0">
<span>Learn more</span>
<svg class="icon" viewBox="0 0 32 32" aria-hidden="true">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><!-- ... --></g>
</svg>
</a>
For the next section, let’s use the Sticky Hero component – the ‘scale’ variation – with some minor customisations.
First, we’ll copy the HTML, CSS and JS code of the component in our project.
In the HTML, we can update the content of the .sticky-hero__content
to include a <blockquote>
element:
<div class="sticky-hero__content">
<blockquote class="container max-width-sm text-center color-white">
<p class="text-xxl">One belongs to New York instantly, one belongs to it as much in five minutes as in five years.</p>
<footer class="margin-top-lg">— Tom Wolfe</footer>
</blockquote>
</div>
Now let’s create the gallery section. The HTML will include a heading element and images:
<div class="container max-width-adaptive-lg padding-y-xl">
<div>
<h2 class="text-md font-medium border-top padding-top-xs">Selected Works</h2>
<figure>
<img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
</figure>
<figure>
<img src="assets/img/gallery-img-2.jpg" alt="Image Preview">
</figure>
<figure>
<img src="assets/img/gallery-img-3.jpg" alt="Image Preview">
</figure>
<figure>
<img src="assets/img/gallery-img-4.jpg" alt="Image Preview">
</figure>
</div>
</div>
On mobile, we want the elements to be one below the other, while on a bigger screen, they can be positioned to create a grid. Again, we’ll make use of the grid utility classes:
<div class="container max-width-adaptive-lg padding-y-xl">
<div class="grid gap-xs">
<h2 class="col-4@md text-md font-medium border-top padding-top-xs">Selected Works</h2>
<figure class="col-8@md">
<img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
</figure>
<figure class="col-8@md">
<img src="assets/img/gallery-img-2.jpg" alt="Image Preview">
</figure>
<figure class="col-4@md">
<img src="assets/img/gallery-img-3.jpg" alt="Image Preview">
</figure>
<figure class="offset-1@md col-10@md">
<img src="assets/img/gallery-img-4.jpg" alt="Image Preview">
</figure>
</div>
</div>
You can play with the .col-{number}
and .offset-{number}
classes to create a different grid layout!
To animate the gallery images as they enter the viewport, let’s use the Reveal Effects component.
Of the different animations, we’ll use the rotate-up. Make sure to include the CSS and JS of the component, then add the .reveal-fx.reveal-fx--rotate-up
classes, as explained in the Info page:
<figure class="col-8@md reveal-fx reveal-fx--rotate-up">
<img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
</figure>
For the next section, we are going to create a single-column layout on mobile, and a two-columns on bigger screens:
<div class="bg-dark padding-y-xl">
<div class="container max-width-adaptive-lg">
<div class="grid gap-md">
<div class="col-4@md">
<!-- content of first column -->
</div>
<div class="col-8@md">
<!-- content of second column -->
</div>
</div>
</div>
</div>
Now we can add a heading element to the first column and some text elements to the second column.
<div class="col-4@md">
<h3 class="text-xxl">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</h3>
</div>
<div class="col-8@md">
<div class="text-component">
<p>Lorem ipsum dolor sit amet...</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
<a href="#0">Read more</a>
</div>
</div>
The .text-component class is used to stylise blocks containing typography, taking care of vertical rhythm and styling inline elements.
To complete this section, let’s add a hover effect to the link at the bottom of the second column, as we did for the link in the top hero section! The CSS of the Link Effects component has already been included, we only need to copy the new HTML and replace the original link:
<div class="col-8@md">
<div class="text-component">
<p>Lorem ipsum dolor sit amet...</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>
<a class="link-fx-1 color-contrast-higher" href="#0">
<span>Read more</span>
<svg class="icon" viewBox="0 0 32 32" aria-hidden="true">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><!-- ... --></g>
</svg>
</a>
</div>
</div>
Our page is almost complete! We still have two sections + the footer
For those sections, we can use three components from the UI library:
- The Stacking Cards component – the ‘bg-images’ variation;
- The Newsletter component;
- The Main Footer component.
And here’s the complete page:
https://ambercreative.co/codrops/
CodyFrame comes with a built-in dark theme! To activate dark more, add the data-theme="dark"
to the body element, no additional changes are required!
<body data-theme="dark">
<!-- page content here -->
</body>
Here’s a preview of the same page in dark mode:
https://ambercreative.co/codrops/dark-theme.html
You can get the complete code on GitHub.
Hope you enjoyed this article! We’re always open to feedback and suggestions! Share your thoughts in the comments or message us on Twitter.