Extending parent styles in Sass to handle component states

It's a quite frequent case that some components like input fields have many states. It may look different according to some conditions. The classic example is an incorrect value in an input field which results in a red border. How to handle this in Sass?

Extending parent styles in Sass to handle component states

Say we have a text input that can be either in the valid or invalid state. If validation fails, we would like to add a red border indicating that something is wrong with the value that the user has provided. We do this by adding --error suffix to the class name.

<!-- correct value --><input class="my-input" type="text" />
<!-- validation error (red border) --><input class="my-input--error" type="text" />
HTML

Using @extend directive

The @extend directive allows us to include all styles from another class and broaden them with new properties. It slightly bears a resemblance to inheritance in other languages.

.my-input {    font-size: 14px;    padding: 2px 4px;
    &--error {        @extend .my-input;        border-color: red;    }}
SCSS

It will be transpiled into:

.my-input,.my-input--error {    font-size: 14px;    padding: 2px 4px;}
.my-input--error {    border-color: red;}
CSS

Don't use parent selector in @extend

Note that Sass won't be able to transpile your code if you try to use parent selector & in the @extend directive. The extended class name has to be provided explicitly.

&--error {    @extend &;    border-color: red;}
SCSS

Sass compiler will throw an error like this:

Error: Parent selectors aren't allowed here.7 │         @extend &;  │                 ^  index.scss 7:17  root stylesheet
Error

Alternative approach 1: Using placeholders

You can achieve the same with placeholders. Look at the code below. We assume that all properties that the .my-input class uses are defined in the %my-input placeholder.

%my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input {    @extend %my-input;
    &--error {        @extend %my-input;        border-color: red;    }}
SCSS

The transpilation result is almost the same as with the previous solution. The order of class names in the comma-separated selector differs, but it doesn't matter from the technical point of view.

.my-input--error,.my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input--error {    border-color: red;}
CSS

Additional parent properties with placeholders

Interestingly with the "placeholders" approach, you can add additional properties to the parent that won't be part of the child. Let's add a green border if the input is valid.

Placeholder approach - green border

Consider this example:

%my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input {    @include %my-input;    border-color: green;
    &--error {        @include %my-input;        border-color: red;    }}
SCSS

It results in the CSS like below. The child does not have green border property.

.my-input--error,.my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input {    border-color: green;}
.my-input--error {    border-color: red;}
CSS

Alternative approach 2: Using @include (mixins)

Yet another way is mixins, more specifically the @include directive. Look at the code below.

@mixin my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input {    @include my-input;
    &--error {        @include my-input;        border-color: red;    }}
SCSS

As a result, the Sass compiler will produce:

.my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input--error {    font-size: 14px;    padding: 2px 4px;    border-color: red;}
CSS

Additional parent properties with mixins

The "mixins" approach lets you add properties in the same way as placeholders.

@mixin my-input {    font-size: 14px;    padding: 2px 4px;}
.my-input {    @include my-input;    border-color: green;
    &--error {        @include my-input;        border-color: red;    }}
SCSS

As a result, we've got this:

.my-input {    font-size: 14px;    padding: 2px 4px;    border-color: green;}
.my-input--error {    font-size: 14px;    padding: 2px 4px;    border-color: red;}
CSS

Conclusion

Which way to choose? For me, the first example with the @extend directive is more readable and easier to understand. Nevertheless, if you find placeholders or mixins more suitable, feel free to go with this. The placeholders are similar to mixins, but they produce more optimized CSS code. The @include directive just copies all properties to the separate blocks and results in more extensive transpilation result.

Tagged: Front-end

Comments

The comments system is based on GitHub Issues API. Once you add your comment to the linked issue on my GitHub repository, it will show up below.