Skip to content
The free-flowing framework
CSS JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
docs Update dependencies Dec 6, 2019
src Remove zero padding on similar adjacent sections Dec 5, 2019
.gitignore Move demo into ./docs for compatibility with GitHub Pages Oct 15, 2019
mix-manifest.json Move demo into ./docs for compatibility with GitHub Pages Oct 15, 2019
package.json Make normalize.css a production dependency Dec 6, 2019 Rename from FluidCSS to H2OCSS Oct 16, 2019
webpack.mix.js Move demo into ./docs for compatibility with GitHub Pages Oct 15, 2019
yarn.lock Update dependencies Dec 6, 2019

H2Ocss SASS Framework

Water. It's the stuff of life. Pure. Clear. Free.

H2Ocss takes it's inspiration from this refreshing substance. It's a CSS framework built with SCSS.

  • Simple. It doesn't try to be other things. It doesn't try to do everything. It just gives you a starting point that's uncomplicated but clean. What your site looks like should be up to you, not your framework. We don't give you a million components you might not use, just the basics. There's also no Javascript, so if you need it you'll have to bring your own.
  • Reassuring. Frameworks don't need to be alien. H2Ocss works on a REM baseline grid, so that everything lines up with everything else. CSS Flexbox and CSS Grid are included too, so you can be sure your layouts are going to be predictable on modern browsers.
  • Clear. All CSS classes use the BEM naming conventions, so that they're namespaced, predictable and clear.


How to install it

Until it finds its way onto Packagist, it'll need to be installed via the Git repo.

npm require --dev

Alternatively you can add the following directly into your package.json file:

  "devDependencies": {
    "h2o-css": ""
npm install

Now you'll need to integrate it into your own CSS. If you're using SASS, it's as easy as this:

@import '~h2o-css';

How to customise it

There are a load of variables in /src/_variables.scss that are used throughout the framework. Feel free to override any as you need to. For example:

$h2o__debug: true;
$h2o__base-unit-size: 8px;
@import '~h2o-css';

Debug mode

If you add $h2o__debug: true; before you import H2Ocss, it will highlight each element so you can see where it starts and ends, and also superimposes grid lines over the top so you can see the baseline grid is being respected.


How it works

The baseline grid

By default, everything is measured in REMs. If you stick with the supplied defaults, you'll get 1rem = 9px, which gives you a 9px grid. Normal text is 2rems. A paragraph has a line-height of 3rems. Most elements have a 2rem margin. A button is 5rems high. The result is that everything lines up beautifully.

Heading sizes are calculated such that each level is 1.2x the size of the heading below it (you can change the growth factor by setting $h2o__type__heading-factor). There is also a font-size() function that you can use (which we use for the headings too), that calculates the font size relative to the base font size (2rem).


By default, nothing has a margin. Wait, what? Yes, you read that correctly. Paragraphs don't have top and bottom margins. At least, not until you need them. Rather than each element always having margins, we prefer to think about margin being contextual, i.e. margin is between consecutive elements. If a paragraph is on its own in a container, there is nothing it needs separating from, so there shouldn't be a margin. But if there are several paragraphs (or other block elements) next to each other, there should be some spacing between them. The spacing is therefore determined by the parent, not the element itself.


Use the $h2o__spaced class on a container and all immediate children will have 2rem of spacing between them.

<div class="h2o__container h2o__spaced">


There are a few default colours set, but you'll probably want to use your own. Here's what you start with:

$h2o__color--white: #fff !default;
$h2o__color--light: #eee !default;
$h2o__color--dark: #222 !default;
$h2o__color--black: #000 !default;
$h2o__color--red: #e00 !default;
$h2o__color--green: #0c0 !default;
$h2o__color--blue: #06e !default;
$h2o__color--yellow: #ee0 !default;
$h2o__color-map: ('red': $h2o__color--red, 'green': $h2o__color--green, 'blue': $h2o__color--blue, 'yellow': $h2o__color--yellow, 'light': $h2o__color--light, 'dark': $h2o__color--dark) !default;

You can override any of those colour variables. You can also add your own, depending on your needs, and if you add your custom colours to $h2o__color-map then it'll be available in various other elements too. For example:

$h2o__color--blue: #1133ff;
$h2o__color--pink: #ff00ff;
$h2o__color--primary: $h2o__color--green;
$h2o__color-map: map_merge($h2o__color-map, ('pink': $h2o__color--pink, 'primary': $h2o__color--primary));

Now you can use your new colours!

<button class="h2o__button h2o__button--primary">I'm a primary button</button>


A section is a full-width element that optionally also defines a background/foreground theme and a size. It typically also contains a container, which sets a maximum width and centres your site.

<section class="h2o__section">


The section colour options are defined by $h2o__color-map. Any of those colours can be used as a BEM-style property, such as h2o__section--blue, and the text colour will invert when it needs to.

There are also a few sizes to choose from, which increase the top and bottom padding. Use --small, --medium and --large properties. Or, if you need a section to be at least as tall as your browser viewport, use --full.

<section class="h2o__section h2o__section--dark h2o__section--large">


In many cases (though of course not all, so this is technically optional) you'll want to set a maximum width for your website. A .h2o__container will give you a central column, with small left and right margins on smaller devices, and with a maximum width on larger screens. You'll typically place a container inside a .h2o__section, but it's up to you.

<div class="h2o__container">



The layout of your site depends entirely on your design, so we try to keep out of the way and let you do your thing. But there are a couple of useful tools you might find helpful.


Add .h2o__grid to a .h2o__container (or any other element) and it'll turn into a CSS Grid. By default, this will give you a certain number of columns, depending on the device size, into which you can put your markup. Out of the box, the grid will give you 4 columns on mobile, 8 columns on tablet and 12 columns on desktop, but of course you can change that if you need to.


As for how wide you need your columns, that's entirely up to you. Here's an example where we want two columns side-by-side:

<section class="h2o__section">
  <div class="h2o__container h2o__grid">
    <div class="half-left">
    <div class="half-right">
@import '~h2o-css';

// Your custom styles
.half-left {
  grid-column: 1 / span 4;
  @include breakpoint('tablet') {
    grid-column: 1 / span 4;
  @include breakpoint('desktop') {
    grid-column: 1 / span 6;

.half-right {
  grid-column: 1 / span 4;
  @include breakpoint('tablet') {
    grid-column: 5 / span 4;
  @include breakpoint('desktop') {
    grid-column: 7 / span 6;


Other times you might want a set of columns, but you don't necessarily know in advance how many. This is where CSS Flexbox comes in handy, and again we've provided some handy tools to get you started.

Add .h2o__flex to a container, and all its immediate children will become columns, with appropriate margins between them. For example, this will give you three equal sized columns:

<div class="h2o__container h2o__flex">


How your form looks and feels is entirely up to you. But we've hopefully set some basics to point you in the right direction.

Typically, a form field is made up of several (sometimes optional) components:

  • A label
  • Some help text, perhaps describing what information is expected
  • The input field itself
  • An error message

We recommend using a div to contain each field. You may see some developers using p elements, usually because that generally gives some padding around each field, but the contents aren't really a paragraph of text, so semantically it doesn't make sense, and we provide the .h2o__spaced class to handle spacing between elements anyway. So, a simple text field might look like this:

<form class="h2o__form h2o__spaced">
    <label for="text1">Name:</label>
    <div class="h2o__form__help">Please give your first and last names.</div>
    <input type="text" id="name" class="h2o__form__input">


Text inputs

<input type="text" class="h2o__form__input">

Each input is 5rems high. To ensure that height doesn't get confused, we use an inset box-shadow to give it an outline rather than a border. When the input is focused, it'll be highlighted. There is also a .h2o__form__input--error class you can apply to give feedback when there's a validation problem.

Text areas

<textarea class="h2o__form__textarea"></textarea>

As with text inputs, these can have the .h2o__form__input--error class applied to them.

Select / dropdown

<div class="h2o__form__select">
    <option value="" hidden disabled selected>Please select an option</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>

To ensure consistent styling, a select must be enclosed in a .h2o__form__select.


Checkboxes are structured slightly differently, having the input before the label.

  <input type="checkbox" class="h2o__form__checkbox" id="checkbox1">
  <label for="checkbox1">Please click me</label>

Behind the scenes, the styling of this hides the default system input and uses ::before on the label to give you a box you can style to your needs.


Like the checkboxes, these need to be enclosed in a container. But in this case, you'll always have multiple radio options in a group to choose between. We recommend using a fieldset to semantically group them together.

  <legend>Choose one of these options:</legend>
    <input type="radio" class="h2o__form__radio" id="radio1" name="radio" value="1">
    <label for="radio1">Option 1</label>
    <input type="radio" class="h2o__form__radio" id="radio2" name="radio" value="2">
    <label for="radio2">Option 2</label>


By default, buttons are 5rems high, and are available in all colours defined in $h2o__color-map. In addition there is an --outlined option. Button text colours will automatically invert to maintain sufficient contrast with the background.

  <a href="#" class="h2o__button h2o__button--light">Link button</a>
  <button class="h2o__button h2o__button--dark">Dark button</button>
  <button class="h2o__button h2o__button__blue h2o__button--outlined">Outlined button</button>


A button is inline-flex, so if you need to include an icon in your button then it's as easy as placing some child spans inside and the alignment will be taken care of.

<button class="h2o__button h2o__button--primary">
  <span>Here is the label</span>
  <span class="h2o__button__icon">
    <img src="path/to/icon" alt="">

Who made this?

That would be me. Hi.

Find me on Twitter: @mafu_d

You can’t perform that action at this time.