Mathias Polligkeit
  • Dev
  • Impro
  • Sheet Music
  • Contact
Nov 26, 2021 (last updated: Dec 2, 2021)

Design Systems, Pt. 7: Project Structure

The previous articles explained the building blocks in detail, but not how to put everything together. Let’s see how all of this would look in a project.

Folder layout

├── _extends.scss
├── _functions.scss
├── _mixins.scss
├── main.scss
├── base
│   ├── _general.scss
│   ├── _index.scss
│   └── _typography.scss
├── components
│   ├── _index.scss
│   └── _pagination.scss
├── export
│   └── _colors.scss
├── layouts
│   ├── _index.scss
│   └── _main-grid.scss
├── themes
│   ├── _base.scss
│   ├── _dark.scss
│   ├── _default.scss
│   ├── _functions.scss
│   └── _index.scss
└── utilities
    ├── _colors.scss
    ├── _index.scss
    └── _spacing.scss

Root

  • _extends.scss: Contains all placeholder selectors.
  • _functions.scss: Contains all functions, except those for theming.
  • _mixins.scss: Contains all mixins, including the one for defining the custom properties.
  • main.scss: The entry point.

main.scss only uses the other modules of the system.

@charset 'utf-8';

@use "base";
@use "components";
@use "layouts";
@use "themes";
@use "utilities";

In the folder tree above you’ll notice the _index.scss files in the sub folders. When you @use a folder, SASS looks for the _index.scss file in that folder. The index files in turn should @use the modules in the same folder.

@use is preferred over @import to restrict the scope of variable, function, and mixin definitions. Unlike @import, variables are not pulled into the global scope when you use @use.

The underscore in file names is a SASS naming convention for partials.

Base

This folder contains global styles. You’ll commonly find these files in that folder:

  • _general.scss
  • _animations.scss
  • _typography.scss
  • _reset.scss or _normalize.scss

Components

Components are the building blocks of the page. This could be a navigation bar, a pagination component, a button etc. Each component gets its own file. Components are about the content, not about the relationship to other components or elements.

Layouts

Layouts describe the relationship between components/elements on a page, but do not describe the styles of the contents. You can think of these as wrappers with slots that can be filled with any arbitrary content. Layouts can also be made up of other smaller layouts. Each layout gets its own file. A layout can be anything between a container that aligns two items next to each other and a full-size grid layout with navbar, sidebar and footer.

Export

This folder contains the export modules for interoperation with JavaScript as outlined in part 6 of the series.

Themes

The theme definitions are explained in detail in part 5 of the series.

Utilities

A collection of utility classes, i.e. classes that do one thing and one thing only. This includes manually defined utility classes (e.g. visibility helpers) and classes generated automatically from the theme configuration, as described in part part 4.

Hints for writing clean CSS

Components and layouts

  • Each component and each layout should get its own file.
  • All styles of a component or layout should be wrapped in a class.
  • All sub classes of a component should be prefixed.
  • Don’t overdo the nesting.
  • Use the child selector where possible.
.pagination {
  & > .pagination-link {
    // ...
  }

  & > .pagination-prev {
    // ...
  }

  & > .pagination-next {
    // ...
  }
}

I prefer not to use a naming convention for components (blocks) like BEM. Just use snake case, and keep the component classes short, since otherwise you’ll get very long names for your sub classes.

Modifier classes

Commonly used naming conventions for modifiers are:

  • --active in BEM
  • -active in RSCSS
  • is-active in frameworks like Bulma

All of them are readable. BEM appends modifier class names to the block and element classes, resulting in long class names. We use the double dash for custom properties now, and maybe it is better to use it in one context only. The combination of a period and a single dash as in RSCSS can look weird if a font with ligatures is used.

is-* is sometimes combined with has-* and are-*. is-* and has-* are good, human-readable names. They are still snake case, so you don’t need exceptions in your linter rules. I would avoid are-* (which is sometimes used if the class name is a plural), since it is easy to mix up the class names, and even if the class name is a plural, it is still a single class.

.pagination {
  & > .pagination-link {
    &.is-current {
      // ...
    }
  }
}

.container {
  &.has-sidebar {
    // ...
  }
}

Using functions, mixins and extends

All theme variables are accessed via the accessor functions. To use them, you will need to @use the module. The same applies to the mixins and extends.

Functions and mixins from another module can only be used with the module name. Placeholder selectors (extends) can be accessed without the module name.

It is recommended to use aliases when using the root modules.

@use "../extends";
@use "../functions" as f;
@use "../mixins" as m;

.some-component {
  @extend %some-placeholder;
  @include m.some-mixin($color: f.color(primary));

  margin-bottom: f.lines(1);
}

Variables in components and variables

Since variables are locally scoped with @use, there is no need to prefix any variables that are specific to a component or layout. You can just put them at the top of the module.

$max-width: 80%;

.container {
  max-width: $max-width;
}

However, you should only use SASS variables if you need to make calculations at compile time, generate styles, or if you need to access a value from another module. In all other cases, it is preferred to use custom properties.

.container {
  --max-width: 80%;

  max-width: var(--max-width);
}

You can use custom properties for modifying styles.

.container {
  max-width: var(--max-width, 80%);

  &.is-narrow {
    --max-width: 50%;
  }
}

Summary

Whatever you do, be consistent. Now keep your classes for layouts (relationships) and components (contents) clean, and that’s it. We fixed CSS.

References

  • Cube CSS
  • RSCSS
  • BEM

Next part: Design Systems, Pt. 8: Layout Rules

  • design-systems
  • sass

See Also

  • Design Systems, Pt. 9: Conclusion
  • Design Systems, Pt. 8: Layout Rules
  • Design Systems, Pt. 6: Variable Export
  • Design Systems, Pt. 5: Themes
  • Design Systems, Pt. 4: Generating Utility Classes
  • privacy policy