# How to customize Storefront UI components

One of the key goals of Storefront UI is to provide you with a ready to use design system that will allow you to recreate almost every design.

Below you can read how you can customize different aspects of its styles and components.

# Intro to CSS Custom Properties

CSS Custom Properties (or so called CSS variables) are extremely powerful and have a great impact on how we write and structure our styles. We decided to migrate from SCSS to CSS variables for many reasons:

  • Easy-theming You can easily overwrite any variable as they are dynamic (unlike variables defined using preprocessors). All elements that use the variable will automatically reflect the change.
.sf-button {
  background: var(--button-background, var(--c-primary));

  &:hover {
    --button-background: var(--c-primary-variant);
  }
}
  • Locally scoped properties Thanks to them we can setup a local property for any component inside parent selector without the need to add additional selectors. For example:
.sf-arrow {
    --button-height: 2.75rem;
    --button-background: var(--c-light);
    --icon-width: 1.5rem;
}
  • Clean, good looking code CSS variables allow us to keep apply all the required styles without need to use ::v-deep or nest many properties and classes inside of parent selector. This is how SCSS file for the same component looked before:
.product-carousel {
  margin: -20px - var(--spacer-big) -20px 0;
  @include for-desktop {
    margin: -20px 0;
  }
  ::v-deep .sf-carousel__wrapper {
    padding: 20px 0;
    @include for-desktop {
      padding: 20px;
      max-width: calc(100% - 216px);
    }
  }
}

And how it looks now, thanks to the power of CSS Custom Properties:

.product-carousel {
    margin: 0 calc(var(--spacer-big) * -1) 0 0;
    @include for-desktop {
      margin: var(--spacer-big) 0;
      --carousel-padding: var(--spacer-big);
      --carousel-max-width: calc(100% - 13.5rem);
    }
  }

To learn more about CSS variables and the default approach we use check out MDN web docs

# Customizing styles

In most scenarios, when you're designing a new app you're starting with a style guide. A style guide is a set of common design standards and principles used in a whole project. It usually covers things such as typography or colors.

We can represent style guide as a set of global CSS variables. By using them we are abstracting visual configuration from html and CSS structure to declarative variables. Thank to this approach we can ship updates to Storefront UI without breaking your projects.

You can override them to shape the look and feel of your project. There are two groups of available CSS variables in Storefront UI:

  • Global variables are representing project style guide. They are setting up global properties like like colors or typography. For example, the following code will change primary font in your whole project to Raleway.
/*
 * use id of DOM root element
 * vue-cli -> #app
 * nuxt-app -> __nuxt
 */
#app {
    --font-family-primary: 'Raleway', serif;
}
  • Component-specific variables are meant to customize behavior of certain component type (like SfButton) and cover edge cases of any project. For example, the following code will change default (not modified by other CSS rules) background color of every SfButton component in your project to red.
.sf-button {
    --button-background: red;
}

And this code will change width of icon inside SfArrow to 2rem.

.sf-arrow {
  --icon-width: 2rem;
}

# Global variables

You can override any global variable as well as component-specific variables.

Below you can find more information about global variables for typography, layout variables (spacers) and colors. To find the components variables to override, go to Components documentation.

# Typography

@import "../helpers";
:root {
  // font-family
  --font-family--primary: "Roboto", serif;
  --font-family--secondary: "Raleway", sans-serif;
  //font-weight
  --font-weight--light: 300;
  --font-weight--normal: 400;
  --font-weight--medium: 500;	
  --font-weight--semibold: 600;
  --font-weight--bold: 700;
  // font-size
  --font-size--xs: 0.75rem; //12px
  --font-size--sm: 0.875rem; //14px
  --font-size--base: 1rem; //16px
  --font-size--lg: 1.125rem; //18px
  // heading font-size
  --h1-font-size: 1.5rem; //24px
  --h2-font-size: 1.5rem; //24px
  --h3-font-size: var(--font-size--lg); //18px
  --h4-font-size: var(--font-size--lg); //18px
  --h5-font-size: var(--font-size--base); //16px
  --h6-font-size: var(--font-size--base); //16px
  @include for-desktop {
    --h1-font-size: 2.625rem; //42px
    --h2-font-size: 2.25rem; //36px
    --h3-font-size: 1.625rem; //26px
    --h4-font-size: 1.25rem; //20px
    --h5-font-size: var(--font-size--base); //16px
    --h6-font-size: var(--font-size--sm); //14px
  }	  
}

# Spacers

:root {
  --spacer-2xs: 0.25rem; // 4px
  --spacer-xs: 0.5rem; // 8px
  --spacer-sm: 1rem; // 16px
  --spacer-base: 1.5rem; // 24px
  --spacer-lg: 2rem; // 32px
  --spacer-xl: 2.5rem; // 40px
  --spacer-2xl: 5rem; // 80px
  --spacer-3xl: 10rem; // 160px
}

# Colors

There are two kinds of color variables, Internal colors and Theme Variables.

Internal colors should NOT be used directly for theming or component styles. They are only used to generate theme color variables.

There are some kinds of Theme Variables:

  • Body and text defaults: They are used to define body, text and links colors.
  • Brand Colors:
    • Primary: Should be used to drive attention to the main tasks that should be done while using the app. It is meant to be used in major interactive elements of the page.
    • Secondary: Should be used to drive attention in the elements with relevant info, but where the user is not intended to take action.
  • Base colors: Light, Gray and Dark.
  • Context colors: Info, Success, Warning and Danger.

# Internal color variables generation

Internal color variables are defined using a SASS mixin that generates all the internal color variants.

This code:

@include generate-color-variants(--_c-green-primary, #5ece7b);

will generate the following variables:

--_c-green-primary: #5fce7d;
--_c-green-primary-base: 95, 206, 125;
--_c-green-primary-lighten: #73d48d;
--_c-green-primary-darken: #4bc86d;

This variables will be used to define the Theme color variables.

# Theme color variables definition

Once we have generated all the internal color variants, we can assign them to the Theme color variables. For example:

@include assign-color-variants(--c-primary, --_c-green-primary);

This code will generate the following variables:

--c-primary: var(--_c-green-primary);
--c-primary-base: var(--_c-green-primary-base);
--c-primary-lighten: var(--_c-green-primary-lighten);
--c-primary-darken: var(--_c-green-primary-darken);

# How to override color variables?

For example:

// Internal color variables
@include generate-color-variants(--_c-blue-primary, #0284fe);
/* Brand colors */
// Primary
@include assign-color-variants(--c-primary, --_c-blue-primary);

# Below, you can find all global color variables that you can use and override.

@import "../helpers/colors";
:root {
  @include generate-color-variants(--c-black, #1d1f22);
  --c-white: #ffffff;
  // Internal color variables
  /* Should NOT be used directly for theming or component styles */
  @include generate-color-variants(--_c-green-primary, #5ece7b);
  @include generate-color-variants(--_c-green-secondary, #9ee2b0);
  @include generate-color-variants(--_c-dark-primary, #1d1f22);
  @include generate-color-variants(--_c-dark-secondary, #43464E);
  @include generate-color-variants(--_c-gray-primary, #72757E);
  @include generate-color-variants(--_c-gray-secondary, #8D8F9A);
  @include generate-color-variants(--_c-gray-accent, #e0e0e1);
  @include generate-color-variants(--_c-light-primary, #f1f2f3);
  @include generate-color-variants(--_c-light-secondary, #ffffff);
  @include generate-color-variants(--_c-red-primary, #d12727);
  @include generate-color-variants(--_c-red-secondary, #fcede8);
  @include generate-color-variants(--_c-yellow-primary, #ecc713);
  @include generate-color-variants(--_c-yellow-secondary, #f6e389);
  @include generate-color-variants(--_c-blue-primary, #0468DB);
  @include generate-color-variants(--_c-blue-secondary, #e1f4fe);

  /* Theme color variables */
  // Body and text defaults
  --c-body: var(--_c-light-secondary);
  --c-text: var(--_c-dark-primary);
  --c-text-muted: var(--_c-gray-primary);
  --c-text-disabled: var(--_c-gray-accent);
  --c-link: var(--_c-dark-secondary);
  --c-link-hover: var(--c-black);

  /* Brand colors */
  // Primary
  /* Should be used to drive attention to the main tasks that should be done while using the app.
   * It is meant to be used in major interactive elements of the page. */
  @include assign-color-variants(--c-primary, --_c-green-primary);
  --c-primary-variant: var(--_c-green-secondary);
  --c-on-primary: var(--_c-light-secondary);
  // Secondary
  /* Should be used to drive attention in the elements with relevant info,
   * but where the user is not intended to take action. */
  @include assign-color-variants(--c-secondary, --_c-dark-primary);
  --c-secondary-variant: var(--_c-dark-secondary);
  --c-on-secondary: var(--_c-light-secondary);

  /* Base colors*/
  // Light
  @include assign-color-variants(--c-light, --_c-light-primary);
  --c-light-variant: var(--_c-light-secondary);
  --c-on-light: var(--c-black);
  // Gray
  @include assign-color-variants(--c-gray, --_c-gray-primary);
  --c-gray-variant: var(--_c-gray-secondary);
  --c-on-gray: var(--c-black);
  // Dark
  @include assign-color-variants(--c-dark, --_c-dark-primary);
  --c-dark-variant: var(--_c-dark-secondary);
  --c-on-dark: var(--_c-light-secondary);

  /* Context colors */
  // Info
  @include assign-color-variants(--c-info, --_c-blue-primary);
  --c-info-variant: var(--_c-blue-secondary);
  --c-on-info: var(--_c-light-secondary);
  // Success
  @include assign-color-variants(--c-success, --_c-green-primary);
  --c-success-variant: var(--_c-green-secondary);
  --c-on-success: var(--_c-light-secondary);
  // Waring
  @include assign-color-variants(--c-warning, --_c-yellow-primary);
  --c-warning-variant: var(--_c-yellow-secondary);
  --c-on-warning: var(--_c-light-secondary);
  // Danger
  @include assign-color-variants(--c-danger, --_c-red-primary);
  --c-danger-variant: var(--_c-red-secondary);
  --c-on-danger: var(--_c-light-secondary);
}

# Per-instance component customization

Even though global and component-specific variables are providing decent level of customization, there might be edge cases that a user would like to cover as well. You can achieve that with vue slots.

Almost every Storefront UI component is divided into sections (following BEM convention). Each of them is wrapped into a Vue slot to let you replace parts of it's HTML.

Take a look at below example. This is how SfPagination component look like out of the box:


<SfPagination
  :current="currentPage"
  :total="20"
  :visible="5"
  @click="page => currentPage = page"
/>

Let's say we want to display prev and next buttons instead of default arrow icons.

In component documentation we can read that it has next and prev slots. We can use them to add our custom behavior. For every slot you have access to methods and data properties related to it's functionality via slot scope. In our case it would be go function that will move forward/backward and isDisabled property that tells us if there is a next or previous page. We can use the latest to disable buttons when they're not usable.

<SfPagination :current="currentPage" :total="20" :visible="5"  @click="page => currentPage = page">
  <template #prev="{ isDisabled, go }">
    <button @click="go()" :disabled="isDisabled ? true : false">prev</button>
  </template>
  <template #next="{ isDisabled, go }">
    <button @click="go()" :disabled="isDisabled ? true : false">next</button>
  </template>
</SfPagination>

This is a result of above modification:

The same way you can customize any other Storefront UI component. Default markup is no longer a problem for us!

# Tailwind customization

You can use the tailwind to customize StorefrontUI components in your application.

To do this, first install the Vue tailwind in your project (if you are using Vue CLI):

vue add tailwind

Then create or edit an existing tailwind.config.js file. To use the variables provided by StorefrontUI, below is an example of what such a file might look like:

module.exports = {
  purge: [],
  theme: {
    extend: {
      fontSize: {
        'sf-xs': 'var(--font-size--xs)', //12px
        'sf-sm': 'var(--font-size--sm)', //14px
        'sf-base': 'var(--font-size--base)', //16px
        'sf-lg': 'var(--font-size--lg)', //18px
      },
      fontWeight: {
        'sf-light': 'var(--font-weight--light)', //300
        'sf-normal': 'var(--font-weight--normal)', //400
        'sf-medium': 'var(--font-weight--medium)', //500
        'sf-semibold': 'var(--font-weight--semibold)', //600
        'sf-bold': 'var(--font-weight--bold)', //700
      },
      colors: {
        'sf-c-black': 'var(--c-black)', // #1d1f22
        'sf-c-black-base': 'var(--c-black-base)', // #1d1f22
        'sf-c-black-lighten': 'var(--c-black-lighten)', // #292c30
        'sf-c-black-darken': 'var( --c-black-darken)', // #111214
        'sf-c-white': 'var(--c-white)', // #ffffff
        'sf-c-body': 'var(--c-body)', // #ffffff
        'sf-c-text': 'var(--c-text)', // #1d1f22
        'sf-c-text-muted': 'var(--c-text-muted)', // #72757E
        'sf-c-text-disabled': 'var(--c-text-disabled)', // #e0e0e1
        'sf-c-link': 'var(--c-link)', // #43464E
        'sf-c-link-hover': 'var(--c-link-hover)', // // #1d1f22
        'sf-c-primary': 'var(--c-primary)', // #5ece7b
        'sf-c-primary-base': 'var(--c-primary-base)', // #5ece7b
        'sf-c-primary-lighten': 'var(--c-primary-lighten)', // #72d48b
        'sf-c-primary-darken': 'var(--c-primary-darken)', // #4ac86b
        'sf-c-primary-variant': 'var(--c-primary-variant)', // #9ee2b0
        'sf-c-on-primary': 'var(--c-on-primary)', // #ffffff
        'sf-c-secondary': 'var( --c-secondary)', // #1d1f22
        'sf-c-secondary-base': 'var(--c-secondary-base)', // #1d1f22
        'sf-c-secondary-lighten': 'var(--c-secondary-lighten)', // #292c30
        'sf-c-secondary-darken': 'var(--c-secondary-darken)', // #111214
        'sf-c-secondary-variant': 'var(--c-secondary-variant)', // #43464E
        'sf-c-on-secondary': 'var(--c-on-secondary)', //  #ffffff
        'sf-c-light': 'var(--c-light)', // #f1f2f3
        'sf-c-light-base': 'var(--c-light-base)', // #f1f2f3
        'sf-c-light-lighten': 'var(--c-light-lighten)', // #ffffff 
        'sf-c-light-darken': 'var(--c-light-darken)', // #e3e5e7
        'sf-c-light-variant': 'var(--c-light-variant)', //  #ffffff
        'sf-c-on-light': 'var(--c-on-light)', // #1d1f22
        'sf-c-gray': 'var(--c-gray)', // #72757E
        'sf-c-gray-base': 'var(--c-gray-base)', // #72757E
        'sf-c-gray-lighten': 'var(--c-gray-lighten)', // #7f828b
        'sf-c-gray-darken': 'var(--c-gray-darken)', // #666971
        'sf-c-gray-variant': 'var(--c-gray-variant)', // #8D8F9A
        'sf-c-on-gray': 'var(--c-on-gray)', // #1d1f22
        'sf-c-dark': 'var(--c-dark)', // #1d1f22
        'sf-c-dark-base': 'var(--c-dark-base)', // #1d1f22
        'sf-c-dark-lighten': 'var(--c-dark-lighten)', // #292c30
        'sf-c-dark-darken': 'var(--c-dark-darken)', // #111214
        'sf-c-dark-variant': 'var(--c-dark-variant)', // #43464E
        'sf-c-on-dark': 'var(--c-on-dark)', //  #ffffff
        'sf-c-info': 'var(--c-info)', //  #0468DB
        'sf-c-info-base': 'var(--c-info-base)', //  #0468DB
        'sf-c-info-lighten': 'var(--c-info-lighten)', // #0474f4
        'sf-c-info-darken': 'var(--c-info-darken)', // #045cc2
        'sf-c-info-variant': 'var(--c-info-variant)', // #e1f4fe
        'sf-c-on-info': 'var(--c-on-info)', //  #ffffff
        'sf-c-success': 'var(--c-success)', // #5ece7b
        'sf-c-success-base': 'var(--c-success-base)', // #5ece7b
        'sf-c-success-lighten': 'var(--c-success-lighten)', // #72d48b
        'sf-c-success-darken': 'var(--c-success-darken)', // #4ac86b
        'sf-c-success-variant': 'var(--c-success-variant)', // #9ee2b0
        'sf-c-on-success': 'var(--c-on-success)', //  #ffffff
        'sf-c-waring': 'var(--c-waring)', // #ecc713
        'sf-c-waring-base': 'var(--c-waring-base)', // #ecc713
        'sf-c-waring-lighten': 'var(--c-waring-lighten)', //  #eecd2b
        'sf-c-waring-darken': 'var(--c-waring-darken)', // #d4b311
        'sf-c-waring-variant': 'var(--c-waring-variant)', // #f6e389
        'sf-c-on-waring': 'var(--c-on-waring)', //  #ffffff
        'sf-c-danger': 'var(--c-danger)', //  #d12727
        'sf-c-danger-base': 'var(--c-danger-base)', //  #d12727
        'sf-c-danger-lighten': 'var(--c-danger-lighten)', // #da3838
        'sf-c-danger-darken': 'var(--c-danger-darken)', //  #bc2323
        'sf-c-danger-variant': 'var(--c-danger-variant)', // #fcede8
        'sf-c-on-danger': 'var(--c-on-danger)', // #ffffff
      },
      spacing: {
        'sf-2xs': 'var(--spacer-2xs)', // 4px
        'sf-xs': 'var( --spacer-xs)', // 8px
        'sf-sm': 'var(--spacer-sm)', // 16px
        'sf-base': 'var(--spacer-base)', // 24px
        'sf-lg': 'var(--spacer-lg)', // 32px
        'sf-xl': 'var(--spacer-xl)', // 40px
        'sf-2xl': 'var(--spacer-2xl)', // 80px
        'sf-3xl': 'var(--spacer-3xl)',  // 160px
      },
      fontFamily: {
        'sf-primary': 'var(--font-family--primary)', // "Roboto", serif
        'sf-secondary': `var(--font-family--secondary)`, // "Raleway", sans-serif
      },
    },
  },
  variants: {},
  plugins: [],
}

This way, StorefrontUI components can then be styled using the tailwind classes:

<SfButton class="bg-sf-c-info font-sf-primary text-sf-lg font-sf-light text-sf-c-light-darken p-sf-lg">Hello</SfButton>

You can create your own classes by mixing @apply with CSS Custom Properties:

<SfButton class="awesome-button-class">Hello</SfButton>
.awesome-button-class {
  --button-cursor: crosshair;
  @apply bg-sf-c-danger font-sf-secondary text-sf-sm font-sf-bold text-sf-c-black-darken p-sf-sm;
}