Skip to content

Instantly share code, notes, and snippets.

@robdecker
Last active January 29, 2021 20:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robdecker/1d69777a9fb085d48c94dcd6c7a8e5c2 to your computer and use it in GitHub Desktop.
Save robdecker/1d69777a9fb085d48c94dcd6c7a8e5c2 to your computer and use it in GitHub Desktop.
[Breakpoint & grid system] #sass

Breakpoint & grid system

Uses @include-media and some Bootstrap grid mixins (but has no Bootstrap dependencies).

Example usage

Tiles component

<div class="tiles">
  <div class="tiles__tiles">
    {% for tile in tiles %}
      <div class="tiles__tile">
        {{ tile }}
      </div>
    {% endfor %}
  </div>
</div>
.tiles {
  @include container;
  margin-top: 30px;
  margin-bottom: 30px;

  &__tiles {
    @include media('>=md') {
      @include row;
    }
  }

  &__tile {
    margin-bottom: 30px;

    @include media('>=md') {
      @include col(50%);
    }

    @include media('>=lg') {
      @include col(33.33333%, 50px);
    }
  }
}
/**
* Bootstrap compatible breakpoint configuration
* @see https://getbootstrap.com/docs/4.0/layout/grid/#grid-options
*/
@import 'grid-functions';
// Grid breakpoints (default)
//
// Define the minimum dimensions at which your layout will change,
// adapting to different screen sizes, for use in media queries.
$xs: 0;
$small: 576px;
$medium: 768px;
$large: 992px;
$menu: 1050px; // Menu tweak
$xl: 1200px;
$max-width: $xl;
$grid-breakpoints: (
xs: $xs,
sm: $small,
md: $medium,
lg: $large,
xl: $xl
);
// Default grid gutter width
$grid-gutter-width: 30px;
// @include-media breakpoints
//
// This is a required variable for that library.
// This also includes any width tweaks.
$breakpoints: (
xs: $xs,
sm: $small,
md: $medium,
lg: $large,
menu: $menu,
xl: $xl
);
// Grid containers
//
// Define the maximum width of `.container` for different screen sizes.
$container-max-widths: (
sm: $small - 36px, // 540px
md: $medium - 48px, // 720px
lg: $large - 32px, // 960px
xl: $xl - 60px // 1140px
);
$container-max-widths--reversed: map-reverse($container-max-widths);
$container-max-widths--main-menu: (
sm: $small - 36px, // 540px
md: $medium - 48px, // 720px
lg: $large - 32px, // 960px
menu: $menu - 90px, // 960px
xl: $xl - 60px // 1140px
);
$container-max-widths--gte-md: (
md: get-container-max-width('md'),
lg: get-container-max-width('lg'),
xl: get-container-max-width('xl'),
);
$container-max-widths--gte-lg: (
lg: get-container-max-width('lg'),
xl: get-container-max-width('xl'),
);
$container-max-widths--gte-xl: (
xl: get-container-max-width('xl'),
);
$container-max-widths--lte-md: (
md: get-container-max-width('md'),
sm: get-container-max-width('sm')
);
// Mixin to return a container widths.
// >> container(80); // for 80%
// >> container($max-widths: $container-max-widths--gte-lg); // for 100%, but only for lg & xl
@mixin container($percent: 100, $max-widths: $container-max-widths) {
@include make-container();
@each $breakpoint, $container-max-width in $max-widths {
@include media('>=#{$breakpoint}') {
max-width: get-container-max-width(#{$breakpoint}) * $percent * 0.01;
}
}
}
// Mixin to return a container widths in reverse.
// >> container(80); // for 80%
// >> container($max-widths: $container-max-widths--lt-md); // for 100%, but only for sm & xs
@mixin container-down($percent: 100, $max-widths: $container-max-widths--reversed) {
$widest: nth(map-keys($max-widths), 1);
@include media('<#{$widest}') {
@include make-container();
}
@each $breakpoint, $container-max-width in $max-widths {
$previous-breakpoint: breakpoint-previous($breakpoint);
@if map-has-key($max-widths, $previous-breakpoint) {
@include media('<#{$breakpoint}') {
max-width: get-container-max-width(#{$previous-breakpoint}) * $percent * 0.01;
}
}
}
}
// $gutter in 'px'
@mixin row($gutter: $grid-gutter-width) {
display: flex;
flex-wrap: wrap;
margin-right: -#{$gutter / 2};
margin-left: -#{$gutter / 2};
}
// $size in '%'
// $gutter in 'px'
@mixin col($size: false, $gutter: $grid-gutter-width) {
position: relative;
width: 100%;
padding-right: $gutter / 2;
padding-left: $gutter / 2;
@if $size {
flex: 0 0 $size;
max-width: $size;
}
}
// $gutter in 'px'
@mixin make-container($gutter: $grid-gutter-width) {
width: 100%;
margin-right: auto;
margin-left: auto;
padding-right: $gutter / 2;
padding-left: $gutter / 2;
}
// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
// Makes the @content apply to the given breakpoint and wider.
@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {
$min: breakpoint-min($name, $breakpoints);
@if $min {
@include media('>=#{$min}') {
@content;
}
} @else {
@content;
}
}
// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
// Makes the @content apply to the given breakpoint and narrower.
@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {
$max: breakpoint-max($name, $breakpoints);
@if $max {
@include media('<#{$max}') {
@content;
}
} @else {
@content;
}
}
// Media that spans multiple breakpoint widths.
// Makes the @content apply between the min and max breakpoints
@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {
$min: breakpoint-min($lower, $breakpoints);
$max: breakpoint-max($upper, $breakpoints);
@if $min != null and $max != null {
@include media('>=#{$min}', '<#{$max}') {
@content;
}
} @else if $max == null {
@include media-breakpoint-up($lower, $breakpoints) {
@content;
}
} @else if $min == null {
@include media-breakpoint-down($upper, $breakpoints) {
@content;
}
}
}
// Media between the breakpoint's minimum and maximum widths.
// No minimum for the smallest breakpoint, and no maximum for the largest one.
// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {
$min: breakpoint-min($name, $breakpoints);
$max: breakpoint-max($name, $breakpoints);
@if $min != null and $max != null {
@include media('>=#{$min}', '<#{$max}') {
@content;
}
} @else if $max == null {
@include media-breakpoint-up($name, $breakpoints) {
@content;
}
} @else if $min == null {
@include media-breakpoint-down($name, $breakpoints) {
@content;
}
}
}
// Display the active breakpoint's name in the bottom right of the viewport.
@mixin display-breakpoints {
body {
&::before {
position: fixed;
right: 3px;
bottom: 3px;
padding: 1px 9px 3px;
border-radius: 3px;
background-color: #aaa;
color: #fff;
font-size: 13px;
opacity: 0.5;
z-index: 9999;
// Loop through the breakpoint
// @include-media breakpointss that should be shown
@each $bp-name, $bp-width in $breakpoints {
@include media('>=#{$bp-name}') {
content: '#{$bp-name}';
}
}
}
}
}
/**
* Helper functions for making grids
*/
// Function to return a container width from $container-max-widths.
// >> get-container-max-width(sm)
// 540px
@function get-container-max-width($name, $container: $container-max-widths) {
@if map-has-key($container, $name) {
@return map-get($container, $name);
}
@warn 'Container width `#{$name}` does not exist.';
@return null;
}
// Name of the next breakpoint, or null for the last breakpoint.
//
// >> breakpoint-next(sm)
// md
// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
// md
// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))
// md
@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {
$n: index($breakpoint-names, $name);
@return if($n != null and $n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);
}
// Name of the previous breakpoint, or null for the last breakpoint.
//
// >> breakpoint-previous(lg)
// md
// >> breakpoint-previous(lg, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
// md
// >> breakpoint-previous(lg, $breakpoint-names: (xs sm md lg xl))
// md
@function breakpoint-previous($name, $breakpoint-map: $grid-breakpoints, $breakpoint-names: map-keys($breakpoint-map)) {
$n: index($breakpoint-names, $name);
@return if($n != null and $n > 1 and $n <= length($breakpoint-names), nth($breakpoint-names, $n - 1), null);
}
// Minimum breakpoint width. Null for the smallest (first) breakpoint.
//
// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
// 576px
@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {
$min: map-get($breakpoints, $name);
@return if($min != 0, $min, null);
}
// Maximum breakpoint width. Null for the largest (last) breakpoint.
// The maximum value is calculated as the minimum of the next one less 0.02px
// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.
// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
// See https://bugs.webkit.org/show_bug.cgi?id=178261
//
// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
// 767.98px
@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {
$next: breakpoint-next($name, $breakpoints);
@return if($next, breakpoint-min($next, $breakpoints) - 0.02, null);
}
// Reverse a map object.
@function map-reverse($map) {
$result: null;
@if type-of($map) == "map" {
$keys: map-keys($map);
$map-reversed: ();
@for $i from length($keys) through 1 {
$map-reversed: map-merge(
$map-reversed,
(nth($keys, $i): map-get($map, nth($keys, $i)))
);
}
@if type-of($map-reversed) == "map" {
$result: $map-reversed;
} @else {
@warn 'There was an error reversing the order of "#{$map}"';
}
} @else {
@warn '"#{$map}" is not a valid map';
}
@return $result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment