DEV Community

Cover image for Separate layout from content
Jonathan
Jonathan

Posted on • Updated on

Separate layout from content

Just a quick post, to share a simple styling practice I've been applying for some time now: always separate layout styling from other styling.

Suppose I want to render a layout with one large item and two smaller ones:

  +-------------------------------------------------------+
  | #item-name            | #edit-button | #delete-button |
  +-------------------------------------------------------+

The bad way

I could style the elements like this:

#container { display: flex; }
#item-name { flex: 1; /* ...other styles */ }
#edit-button { flex: .2; /* ...other styles */ }
#delete-button { flex: .2; /* ...other styles */ }

Notice how in the above snippet, layout styling is spread out between the container and the chidren. There are at least two practical maintainability issues with this:

  • If I decide to swap out one of the children for another, then I have to remember to style the new element with the same flex as the previous one, in order to maintain the layout. For example, if I delete #item-name and its styling and create a new element in its place, e.g. #item-image, then I have to remember to apply flex: 1 to #item-image.

  • If I ever want to change the layout, I potentially have to modify many different elements in different parts of the code. For example, if I want to change the layout so that all the elements have an equal width, then I may have to hunt through many lines of code, in order to find and change the flex properties of the #item-name, #edit-button and #delete-button selectors.

The basic issue is that code concerned with layout is mixed in with code concerned with individual elements. This code has poor separation of concerns. Each "module" or style declaration forces me to think about multiple concerns at once (i.e. layout and component-specific concerns), so that it's difficult to make a change without impacting multiple modules. This is an example of a violation of the Single responsibility principle, which tells us that we should design our modules so that they hide "design decisions which are likely to change" from eachother.

The better way

A better way is to keep all the layout concerns together, separate from the other concerns.

This can be done using the CSS direct-descendent child selector, to select just immediate children, and the :first-child pseudo-selector, to make just the first child fill, while the rest of the children take up 20%.

/* layout styles on container ⬇️ */

#container {
  display: flex;

  > * {
    flex: .2;

    &:first-child {
      flex: 1;
    }
  }
}


/* non-layout styles on individual elements ⬇️ */

#item-name {
  /* item-name styles */
}

#edit-button {
  /* edit-button styles */
}

#delete-button {
  /* delete-button styles */
}

(Note: The above code snippet assumes support for nested styling, using a pre-processor such as Less or SASS.)

Now I can re-order, swap and alter my child components, without having to remember to set their individual widths. That's all taken care of for me by the layout component. I can look at each component's style block and focus just that component's styles, without thinking about the layout.

Likewise, I can change the way the layout looks, without having to review the individual component styles. I could change the orientation to vertical, or make one child larger, or even change the whole layout to use CSS Grid Layout, all without modifying the styling of the individual children.

In the words of Bob Martin, we want to "gather together the things that change for the same reasons ... separate those things that change for different reasons".

Top comments (1)

Collapse
 
bourhaouta profile image
Omar Bourhaouta

A great way to put logic into your styles 👍.

In my case, I carry with me a naked layout style including a small grid system (using CSS Grid for sure 🤘) that fits my needs and I separate layout from elements.

Instead of this:

.container > * { ... }
Enter fullscreen mode Exit fullscreen mode

I'll always be having this:

.container > .column { ... }
Enter fullscreen mode Exit fullscreen mode