-
Notifications
You must be signed in to change notification settings - Fork 7
Project structure and setup
WIP
One of the primary goals for this project is to make it easy to follow, implement and use SEB:s design when building new apps. And some of the core concepts includes being:
- Framework agnostic
- Built on atomic design
- Easy to extend and re-use
- Compatible with Bootstrap
One thing we know for sure is that frameworks for creating web and hybrid apps will change. However, no matter if you use Angular, React, Vue, Stenciljs, jQuery etc. or plain JavaScript in your project, they all have one thing in common and that is that they all rely on html markup and css for layout and styling. They all support css animations although some frameworks support more advanced features like animations with timing, animation events etc.
The goal for this project is to be able to work with all of them and all the JavaScript frameworks of the future that we haven't seen yet. More advanced components like datepickers, dropdowns, modals, accordions etc. relies not only on JavaScript but often on complex markup structures with multiple components which makes it harder to create common styles to cater for all the use cases and implementations, which in turn brings us to the next bullet - Atomic Design.
We aim to follow an Atomic Design Pattern because it not only makes it easier to work with and maintain complex components and markup structures, if used correctly it also helps create a more coherent user experience as bits and pieces of the design are re-used.
One example being a datepicker which usually consists of both a label, input filed, a popover element filled with buttons and maybe dropdowns and other components to. Before adding new things, look at the component to determine if it's an atom, molecule or an organism and try and re-use what we already have.
It's always easier to create and build something new than modifying something that already exists as we don't have conflicting interests and we don't have to worry too much about breaking things as they are independent. Sure that's true, but if we apply the same logic to everything we do we're going to end up at inline styles again and the purpose of a design system and the use of CSS altogether is to ease re-use and simplify design changes as they cascade (from cascading style sheets). There are multiple ways that styles can be shared across components and projects.
This project is structured around these:
- Variables
- Mixins and functions
- Styles (classes)
This is the most basic way and form to share design properties and is ideally used to share things like colors, font-sizes, grid breakpoints, spacing (margin and padding). This project uses scss and all variables are defined as scss variables although some of them are also exported as pure css variables (more on that later).
Bootstrap has a tone of variables defined for things such as border-width
, border-color
, grid-columns
, link-color
and so on. Most of these are pretty standard and since bootstrap is one of the most used Design System in the world it´s battle tested and designed to cater for most user needs and have variables for more things than you thought you'll ever need. You might ask yourself, do I need all these variables and wont they just add complexity and bloat my app? First off, scss variables won't be compiled to css so adding variables won't add any file size. The hard thing with variables is to define and stick to a naming convention and a pattern for the variables and how they should handle inheritance. Luckily for us Bootstrap has already done the hard part for us and we don't have to come of with the structure and the names ourselves. It's not perfect and never will be but it's constantly evolving as the Bootstrap community keeps on refining the framework.
@sebgroup/bootstrap which is our own standalone version inherits all variable from "native" bootstrap. And since they are declared using !default;
we can override them by declaring them before we import them from bootstrap. E.g.
/scss/bootstrap-core/_variables.scss
// gray values declared by bootstrap
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #6c757d !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
/scss/_variables.scss
// gray values declared by @sebgroup/bootstrap
$white: #fff !default;
$gray-100: #f8f8f8 !default;
$gray-200: #e9e9e9 !default;
$gray-300: #dedede !default;
$gray-400: #cecece !default;
$gray-500: #adadad !default;
$gray-600: #868686 !default;
$gray-700: #494949 !default;
$gray-800: #333333 !default;
$gray-900: #1a1a1a !default;
$black: #000 !default;
// import all variables for bootstrap
@import "bootstrap-core/variables";
If a particular app or consumer who in turn imports variables from @sebgroup/bootstrap need to alter a variable (not recommended but better than adding a new one with the same purpose). They could just declare it before importing it from @sebgroup/bootstrap since all variables declared by @sebgroup/bootstrap also uses !default;
.
Variables are good but if they're not attached to a class that we can use in our markup they're of little use to the end user. At some point we have to use them in our code and it's of course possible to do something like this to get a blue button:
// import the variables
@import "@sebgroup/bootstrap/scss/variables";
// declare button class
.button-class {
background: $primary;
// ...other styles
}
Given the following html
<button class="button-class">A button</button>
However this is really hard to maintain and as soon as the structure becomes more complex the ability to re-use becomes quite limited as the actual styling then belongs to the consumer rather than the design system.
Mixins and functions to the rescue!
Mixins and functions Great so instead of giving away the responsibility for assigning css properties to the consumer, lets use mixins and functions. The above example can then be re-written like this as a mixin:
// import the variables
@import "@sebgroup/bootstrap/scss/variables";
// declare a mixin that can be reused
@mixin button() {
background: $primary;
// ...other styles
}
This allows consumers to do this:
// import the mixin
@import "@sebgroup/bootstrap/scss/mixins/_imaginary-button.scss";
// declare button class
.button-class {
// use button mixin
@include button();
}
Given the following html
<button class="button-class">A button</button>
Mixins are essential to any serious design system as they allow us to re-use and apply multiple css properties to one or more elements just by including our mixin! We can also pass variables to our mixin to make it even more dynamic.
All the mixins available in bootstrap can be found here;
Since neither variables, functions or mixins output any css when compiled we recommend including them together (when needed) and to simplify that process we've grouped them into _core.scss
which can be imported using:
@import "~@sebgroup/bootstrap/scss/core";
Great now we have everything we need right? Well what happens with our re-use if we just give people this mixin? We're eventually going to end-up with this:
// import the mixin
@import "@sebgroup/bootstrap/scss/mixins/_imaginary-button.scss";
// declare button class
.button-class {
// use button mixin
@include button();
}
// declare same button class with other name
.same-button-class {
// use button mixin
@include button();
}
/* SEPARATE FILE/PROJECT/COMPONENT */
// import the mixin
@import "@sebgroup/bootstrap/scss/mixins/_imaginary-button.scss";
// declare another button class in another project (in another file)
.button-class {
// use button mixin
@include button();
}
The compiled css will be three times the size compared to sharing styles using a predefined class name! As the final css output would be (unless we remove duplicates of course):
.button-class {
background: #41B0EE;
// ...other styles
}
.same-button-class {
background: #41B0EE;
// ...other styles
}
.button-class {
background: #41B0EE;
// ...other styles
}
Styles To avoid duplicate style declarations and the same rules being sprinkled across multiple class name when not needed (most of the time it's not). Design can be shared using predefined classes. To make it easy to find examples, get help and for new external developers to know our structure this project uses the class names declared by bootstrap with some minor differences that are explained here. SEB:s old design had a Design System called SUIT (SEB UI Toolkit) which was based on Bootstrap 2 so the transition from our old toolkit should be fairly easy for developers accustomed to SUIT as well.
Another benefit of using predefined classes and a framework that is well established in the opensource community is that there are a lot of components that work more or less seamless with our Design System just because they've been written to work with Bootstrap, so instead of having to build all the components ourselves we can rely on these for a lot if not all components:
- ng-bootstrap for angular which we also have wrapper lib for @sebgroup/ng-bootstrap
- react-bootstrap for react
- bootstrap-vue for vue And so on...
All styles (classes) are added by default when you import ~@sebgroup/bootstrap/scss/bootstrap
which we recommend during development as you don't have to think about what to include or leave out. If you have a clear picture of what you need or if you're close to putting your baby to production we'd recommend including only the parts you need.
A list of all the parts that can be imported individually can be found here, please note that you always need to include variables, mixins and functions (they can be imported using @import "~@sebgroup/bootstrap/scss/core";
) before you import the styles you want to add as they are dependent on variables and mixins to compile.
Using classes instead of scss variables for things that we want to keep in sync like colors, paddings, margins etc. allows us to re-use even more code and avoid creating too specific css rules. The downside is of course that the markup gets pretty congested. E.g. consider the following:
.my-button {
color: white;
background: red;
padding: 1rem;
}
.my-badge {
padding: 1rem;
background: red;
border-radius: .25rem;
}
<div class="my-badge">A red badge</div>
<button class="my-button">My red button</button>
vs.
.bg-danger {
background: red;
}
.text-white {
color: white;
}
.p-3 {
padding: 1rem;
}
.rounded {
border-radius: .25rem;
}
<div class="bg-danger rounded p-3">A red badge</div>
<button class="bg-danger text-white p-3">My red button</button>
Both approaches have their pros and cons of course but as the app grows the later tends to be easier to maintain and in the end will generate smaller css bundles as styles are re-used to a greater extent. It's also possible to handle responsiveness and themes using classes.
E.g. bootstrap has classes like p-sm-, p-md- etc. and will create these based on the breakpoints defined in variables.scss where sm stands for small screens, md medium screens and so on.
Introducing something like dark mode or a specific theme for visually impaired people also becomes easier it's possible to do something like:
.dark-mode {
.bg-danger {
background: lightcoral;
}
.text-white {
color: black;
}
}
<body class="dark-mode">
<div class="bg-danger rounded p-3">A red badge</div>
<button class="bg-danger text-white p-3">My red button</button>
</body>
**What about css variables, can't we achieve the same thing using them? **
Well partially, bootstrap actually exposes css variables using root.scss so by including @import "~@sebgroup/bootstrap/scss/styles/root";
or @import "~@sebgroup/bootstrap/scss/bootstrap";
you'll add all colors, grid breakpoints and font-families as css variables at the global scope (:root). It's possible to add all the other properties as well but you'd still have to declare the classes somewhere and unless you redefine the variables on window resize event they won't help you create responsive layouts. They're good for theming though but note also that they're not supported by Internet Explorer:(