Search code examples
cssresponsive-designcss-gridgrid-layout

Using the same css layout but for differently sized media


I use CSS grid and Sass, and I use different grid layouts for different screen sizes (phone, tablet, desktop). But for some pages, I would like the same layouts but chosen at slightly bigger or smaller screens than for other pages.

Is something like that possible? Or am I approaching it from the wrong angle? My current solution (see below) works, but duplicates the styles a lot.

In more detail:

I have 3 different grids that are chosen according to the screen size.

.hero {
    &__container {
        grid-template-areas:
            "header"
            "image"
            "text";
        }
        @media min-width: 1000px {
            grid-template-areas:
                "header header header"
                "text   ...     image"
                "text   ...     image";
            }
            // other definitions for this screen size
        }
        @media min-width: 1300px {
            grid-template-areas:
                "header header image"
                "text   ...    image"
                "text   ...    image";
            }
            // other definitions for this screen size
        }
    }

  &__header {
    grid-area: header;
    font-size: 2.5rem;
    
    @media min-width: 1000px {
        font-size: 2.8rem;
    }
    @media min-width: 1300px {
        font-size: 3.2rem;
    }
  }
  ...
}

They are used in about 20 similar web pages.

<div class="page_a">
    <div class="hero">
        <div class="hero__container">
            <div class="hero__header">...</div>
            <div class="hero__text">...</div>
            <div class="hero__image">...</div>
        </div>
    </div>
</div>

The layout is very similar, but I would like to switch to different layouts at a different point based on the specifics of the content: the header text length, the size & importance of the image, etc.

What I would like to do is something like this:

.page_a {
    .hero {
        // redefine nothing, use defaults
    }
}

.page_c {
    .hero {
        // the header is longer so we need a bigger screen to switch to the biggest layout 
        // somehow say that the 1300px layout should be used from 1500px
    }
}

The only thing I managed to do is to simply redefine all the grids at each possible point (the default points + the custom points), which means the code is very repetitive:

.page_c {
    .hero {
        // use 1000px layout also for 1300px - the whole thing has to be repeated
        @media min-width: 1300px {
            grid-template-areas:
                "header header header"
                "text   ...     image"
                "text   ...     image";
            }
            // other definitions for this size
        }
        // use 1300px layout for 1500px - the whole thing has to be repeated
        @media min-width: 1500px {
            grid-template-areas:
                "header header image"
                "text   ...    image"
                "text   ...    image";
            }
            // other definitions for this size
        }
    }
}

Which means that every time I change some layout I have to go to all the places it is used at various size and change it too.


Solution

  • Your problem could be solved with SASS or SCSS, more precisely with a @mixin. I'm using SCSS because I'm more familiar with it, but you could also use SASS.

    What is a @mixin?

    As stated on the official SASS website:

    Mixins allow you to define styles that can be re-used throughout your stylesheet.

    First you define a @mixin then you call it later in your code with an @include. Every @mixin should have a unique name. For example, define a @mixin layout_600 and call it with an @include layout_600.

    Two things are important when defining a @mixin:

    1. A @mixin should be defined before you call it with an @include. Otherwise, SCSS will try to call something that isn't yet defined (it's defined later in your stylesheet).
    2. A @mixin should be defined outside of your nested code (ideally at the top of your stylesheet). If you define a @mixin inside your nested code, you won't be able to call it later when you want to change default styles. The easiest way for you to understand what I mean is to show you the correct way and the wrong way.

    Correct:

    @mixin layout_600 {
        font-size: 3rem;
        color: blue;
        font-weight: 700;
    }
    
    .hero {
        &__header {
            @media (min-width: 600px) {
                @include layout_600;
            }
        }
    }
    
    .page_b {
        .hero {
            // Use the 600px layout also for the 1000px
            &__header {
                @media (min-width: 1000px) {
                    // This will work
                    @include layout_600;
                }
            }
        }
    }
    

    Wrong:

    .hero {
        &__header {
            @media (min-width: 600px) {
                @mixin layout_600 {
                    font-size: 3rem;
                    color: blue;
                    font-weight: 700;
                }
            }
        }
    }
    
    .page_b {
        .hero {
            // Use the 600px layout also for the 1000px
            &__header {
                @media (min-width: 1000px) {
                    // This will not work
                    @include layout_600;
                }
            }
        }
    }
    

    You need to write a @mixin for each layout that you want to have (e.g., 600px). You only need to do it once for every layout, but you can call a particular @mixin n-times. This is perfect because:

    • You don't have to re-write your code, you just call a particular @mixin with an @include as many times as you want.
    • If you want to change your styling, you do it just once in your @mixin. The style will be changed in every place that is referring to this @mixin.

    Working example

    Before changing the default style

    I defined three @mixins like this:

    • when: window width < 600px,
    • when: 600px < window width < 1000px and
    • when: window width > 1000px.

    Before changing the default style

    As you can see, the font-size and color are different at different window widths. The font is getting bigger, and the color goes from black to blue to red as the window gets wider. By the way, in the right upper corner, I added a div that shows the current window width.

    After changing the default style

    I decided that for page_b I would use the 600px layout (i.e., @mixin layout_600) also for the 1000px. This can easily be done by calling the @mixin layout_600 with the @include layout_600 like this:

    .page_b {
        .hero {
            // Use the 600px layout also for the 1000px
            &__header {
                @media (min-width: 1000px) {
                    @include layout_600;
                }
            }
        }
    }
    

    After changing the default style

    As you can see, the style of the page_b when the window width is actually 1000px is the same as if the window width was 600px (i.e., smaller font and blue color).

    Customizing a @mixin

    Also, it's possible to customize a @mixin if you want. For example, I used the 600px layout (i.e., @mixin layout_600) but changed the color from red to green. This can be done like this:

    .page_b {
        .hero {
            // Use the 600px layout also for the 1000px
            &__header {
                @media (min-width: 1000px) {
                    @include layout_600;
                    color: green; // Customize the mixin
                }
            }
        }
    }
    

    Customizing a @mixin

    As you can see, the color should be blue (as in @mixin layout_600), but it's green.