Styles

Next up we are going to add styles to our not-so-fancy-yet application. We are going to use Sass as it's perhaps the most widely used CSS preprocessor (and you should use one, as it helps you with managing your styles).

Initialize

First we will again add some dependencies to our project

    yarn add -D node-sass sass-lint

to actually build Sass with node-sass and then lint it with sass-lint.

Next we'll create a .sass-lint.yml file to define the conventions for our Sass-files, you can check the available rules here, but this is what I use

options:
  merge-default-rules: false
rules:
  bem-depth:
    - 4
    - max-depth: 4
  class-name-format:
    - allow-leading-underscore: false
    - convention: hyphenatedbem
  declarations-before-nesting: true
  extends-before-declarations: true

from which the first command merge-default-rules indicates that I do not want to use the default rules as a basis, the second defines that for BEM naming the maximum depth is four, the third that I don't allow class names that start with an underscore and that they must follow hyphenatedbem-convention, the fourth that I want style declarations to be before nested selectors and the last that possible @extend must be before style declarations.

Styles.scss

All of our style-sheets will live inside a folder src/styles and the first will be the "main"-stylesheet, called styles.scss (scss is the Sass file extension)

@import 'variables.scss';
@import 'index.scss';
@import 'button.scss';
@import 'todocomponent.scss';
@import 'loader.scss';

body {
    background-color: $tertiary-color;
    font-family: 'Roboto', sans-serif;
}

and at this point it only imports our other (soon-to-be-written stylesheets) so that the compiler will know to bundle them all and sets two styles on our body, a background-color using the variable $tertiary-color (more about variables in a bit) and a font-family of 'Roboto', with a backup font of sans-serif. But wait, what is this 'Roboto' you might ask? It is the main font of Google's Material Design used in Android etc. and a very nice simple font. Now the way to be able to use it in our styles is to add the following line to the head element of our index.html

        <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" />

which will include the font from Google's CDN.

Variables

Next we will define those things we call variables in Sass in their own file called variables.scss

// Colors
$primary-color: #343488;
$secondary-color: #5656AA;
$tertiary-color: #F0F0FF;
$modal-background: rgba(100, 100, 100, .8);

where the underscore in the beginning of the file name is just a convention to differentiate it from regular style files. Inside it we define four different colors (using comments to define the variable "blocks" of colors, sizes etc. is just another convention), a primary color, secondary color, tertiary color and a color for the background of a modal. All variables in Sass must begin with the dollar $ sign.

Index

Next up is the styles for the Index-page, inside a file called index.scss

@import 'variables.scss';

.index {
    align-items: center;
    display: flex;
    flex-direction: column;
    justify-content: space-around;

    &__header {
        color: $primary-color;
    }

    &__form {
        align-items: inherit;
        display: flex;
        flex-direction: inherit;
        justify-content: inherit;

        &__label {
            margin-bottom: 5px;
        }

        &__input {
            background-color: inherit;
            border: 0;
            border-bottom: 1px solid $primary-color;
            margin-bottom: 10px;
            text-align: center;
            width: 250px;

            &:focus {
                border-bottom: 2px solid $secondary-color;
                outline: none;
            }
        }
    }

    &__todo-container {
        display: flex;
        flex-direction: inherit;
        justify-content: flex-start;
    }
}

where we introduce nesting. I will just explain the simplest use case so you understand what is happening

@import 'variables.scss';
.index {
    align-items: center;
    display: flex;
    flex-direction: column;
    justify-content: space-around;

    &__header {
        color: $primary-color;
    }
}

where we see that first we have defined a block for the class index, which styles the flexbox properties for it. Inside we have another block, which starts with &, which is a special character in Sass as it translates the & ampersand to the parent block's selector. So the above would output as CSS something like this:

.index {
    align-items: center;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
}
.index__header {
    color: #343488;
}

This shows us one the uses of BEM as it ties very nicely with the nesting in Sass. You can read more about the reasoning behind BEM here, but the main point is that BEM is as modular and independent as most JavaScript modules, while keeping it similar for every developer.

Button

In a file called button.scss we are going to write our styles for the Button-component

@import 'variables.scss';

.btn {
    background-color: $tertiary-color;
    border: 1px solid $primary-color;
    border-radius: 50%;
    cursor: pointer;
}

which are very simple, like the component itself.

TodoComponent

The styles for our TodoComponent will be set in a file called todocomponent.scss

@import 'variables.scss';

.todo {
    display: flex;
    flex-direction: row;
    margin-bottom: 10px;

    &__checkbox {
        cursor: pointer;
    }

    &__number {
        margin-left: 5px;
    }

    &__title {
        margin-left: 10px;
    }
}

which is a rather simple style as well, just a little flexbox in there.

Loader

Now this is something a bit more interesting, we are going to make our Loader-component finally come to life, by creating the styles for it inside `loader.scss``

@import 'variables.scss';

.loader {
    animation: .8s fadein .2s linear forwards;
    background: $modal-background;
    height: 200vh;
    left: -50vw;
    opacity: 0;
    position: fixed;
    top: -50vh;
    width: 200vw;
    z-index: 1000;

    &__spinner:before {
        animation: spinner .6s linear infinite;
        border: 2px solid #cccccc;
        border-radius: 50%;
        border-top-color: #333333;
        box-sizing: border-box;
        content: '';
        height: 120px;
        left: 50%;
        margin-left: -60px;
        margin-top: -60px;
        position: fixed;
        top: 50%;
        width: 120px;
    }
}

@keyframes spinner {
    to {
        transform: rotate(360deg);
    }
}

@keyframes fadein {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

and this might use some explaining. There are two major points of interest here, the first being CSS animations and the second being the :before selector.


CSS animations are built by setting a value for animation inside the CSS selector

.selector {
    animation: 1s name 2s linear forwards;
}

where the first variable (optional) is the delay for starting the animation, second is the name of the animation (more about that in a bit), third is the length of the animation, fourth is an animation timing function and the last one is animation fill mode.

Now the animation name has to match a @keyframes selector

@keyframes name {
    from {
        opacity: 0;
    }
    50% {
        opacity: 0.5;
    }
    to {
        opacity: 1;
    }
}

where you define the style for the object being styled at different points of the animation (any CSS style is valid here). You can either specify those styles with percentages (such as the 50% here) or as the from and to selectors (from for the first frame and to for the last one) or mix them up.


The :before selector on the other hand is used to create a child (it must have something set to it's content property to show) for the element, that is used to create some kind of styling otherwise impossible to create, for example in our case the actual spinning element.

Scripts

Now that we have our styles set up, we need to include them in our application and to do that, we are going to update our webpack configurations and update our index.tsx-file a bit, beginning with the index.tsx-change, which is to add the following line as the last import statement:

import './styles/styles.scss';

which is because webpack only bundles files related to the entry-file (our index.tsx).


To use webpack with Sass we need to first add a couple of new development dependencies, so go ahead and:

yarn add -D style-loader css-loader sass-loader extract-text-webpack-plugin

where sass-loader compiles our Sass to CSS, css-loader allows webpack to process CSS, style-loader injects said CSS straight into the HTML (faster for development) and extract-text-webpack-plugin extracts our CSS into a single file when doing production builds.

For our development configuration we only need to add the following new rule to webpack.dev.js:

    // ...
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ['style-loader', 'css-loader', 'sass-loader']
            }
        ]
    }

where we tell webpack to process our Sass through the described loaders (from right to left).

For our production build we need to do a few more changes into webpack.prod.js:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
// ...
    module : {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract(['css-loader', 'sass-loader'])
            }
        ]
    },
    // ...
    plugins: [
        new ExtractTextPlugin(path.join('styles.css'))
    ]

where at first we use extract-text-webpack-plugin to extract all processed Sass and then in plugins we tell it to save them in a file called styles.css.

Alternatives

  • Less, another CSS preprocessor
  • Stylus, the "third" CSS preprocessor (Sass and Less are older)
  • PostCSS, a CSS postprocessor

results matching ""

    No results matching ""