rupture

simple media queries for stylus

Usage no npm install needed!

<script type="module">
  import rupture from 'https://cdn.skypack.dev/rupture';
</script>

README

Rupture

npm tests

Simple media queries for stylus.

Note: This project is in early development, and versioning is a little different. Read this for more details.

.test
  color: red

  +below(700px)
    color: blue

Installation

npm install rupture

API Documentation

The first version of this library is very simple, just providing smooth abbreviations for common breakpoints. All of the functions provided by rupture are block mixins, which means that they must be called with a + prefix and a block of stylus should be nested inside them.

Before getting started, I would recommend reading this to better understand the concept that we're trying to hit.

Variables

A few variables are exposed that can be customized, each of them are listed below. All of these variables are scoped under rupture so that there are no conflicts with css keywords or other libraries.

rupture.mobile-cutoff

Pixel value where the mobile mixin kicks in, also the lower bound of the tablet mixin.

rupture.desktop-cutoff

Pixel value where the desktop mixin kicks in, also the upper bound of the tablet mixin.

rupture.hd-cutoff

Pixel value where the hd mixin kicks in, meaning a wider desktop-screen.

rupture.scale

A list of values that you can reference by index in most of the mixins listed below. This works exactly like breakpoint-slicer. Default looks like this:

rupture.scale = 0 400px 600px 800px 1050px 1800px
rupture.scale-names

A list of strings you can reference that correspond to their index location in rupture.scale. This works exactly like breakpoint-slicer

rupture.scale =        0        400px       600px      800px        1050px     1800px

//                     └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────
// Slice numbers:           1           2           3           4           5           6
rupture.scale-names:       'xs'        's'         'm'         'l'         'xl'        'hd'
rupture.scale-names = 'xs' 's' 'm' 'l' 'xl' 'hd'
rupture.enable-em-breakpoints

Enables Rupture's PX to EM unit conversion feature. If set to true, pixel breakpoint values will be automatically converted into em values.

rupture.base-font-size

Determines the conversion factor for converting between px and em/rem values. Will default to the global base-font-size variable if it is defined, or 16px otherwise. For example, if you want to set the conversion factor at 1em = 10px, you can do:

rupture.base-font-size = 10px
html
  font-size: 62.5%
rupture.anti-overlap

Controls Rupture's anti-overlapping feature. Defaults to false.

rupture.density-queries

List of values that controls what conditions to include when creating media queries for high resolution displays. Valid values include 'webkit', 'moz', 'o', 'ratio', 'dpi', and 'dppx' The default list is:

rupture.density-queries = 'dppx' 'webkit' 'moz' 'dpi'

In general, you can set this to 'webkit' 'dpi' to support all modern browsers while limiting the size of the generated media queries.

To create a media query that only targets devices with a high pixel density, either use the density() or retina() mixins or pass a density keyword argument to any of the other mixins. Values passed to the density mixin or keyword argument should be a unitless pixel-ratio (1 for 96dpi, 2 for 192dpi, etc). For example, to target phones with a pixel density of at least 1.25, you can do:

+mobile(density: 1.25)
rupture.retina-density

Value which controls the minimum density of a device considered to have a retina display. Defaults to 1.5. This value will be used when you call the retina() mixin or pass density: retina as a keyword argument to any of the width mixins. For example, to target retina tablets, you can do:

+tablet(density: retina) // equivalent to +tablet(density: 1.5) unless you change rupture.retina-density
rupture.use-device-width

Value which toggles the min-width and max-width media query conditions to min-device-width and max-device-width. defaults to false

Also you can pass it as named argument to override behavior of rupture.use-device-width value:

+to-width(2, use-device-width: true)
rupture.rasterise-media-queries

Value which suppresses media queries that only work in modern browsers. This makes it possible to produce a secondary stylesheet for use with legacy versions of IE.

This is useful because it makes it easier to overcomes the challenge of providing fallback behavior for legacy versions of IE when performing mobile-first development.

Alternative stylesheet can be automatically selected using IE conditional comments:

<!--[if lt IE 9]>
<link rel="stylesheet" href="/css/main-legacy.min.css"/>
<![endif]-->
<!--[if gt IE 8]> -->
<link rel="stylesheet" href="/css/main.min.css"/>
<!-- <![endif]-->

You can automate this process by generating the alternative stylesheet using a bootstrap like the following:

// main-legacy.styl
rasterise-media-queries = true

@import 'main'

Mixins

So there are two "categories" of mixins that are a part of rupture. The first is a very basic set designed to simply shorten and sweeten standard media queries, and the second is a very close port of the fantastic breakpoint-slicer library, which can be used almost as a grid. We'll go through these in order.

By default measurements that are provided to +above, +below, +between and their aliases are inclusive. For instance, this means that +between(300, 500) will satisfy the range of screens width >= 300 and width <= 500. This behaviour can be controlled by adjusting the value of anti-overlap.

+above(measure)

When the screen size is above the provided measure, the styles in the block will take effect.

+from-width(measure)

Alias of above. Styles take effect from the provided measure and above.

+below(measure)

When the screen size is below the provided measure, the styles in the block will take effect.

+to-width(measure)

Alias of below. Styles take effect from zero up to the provided measure.

+between(measure, measure)

When the screen size is between the two provided measure, the styles in the block will take effect.

+at(measure)

Intended for use with scale measures, when the screen size is between the provided scale measure and the one below it, the styles in the block will take effect. For example, if your scale was something like rupture.scale = 0 400px 600px, and you used the mixin like +at(2), it would kick in between 400 and 600px (remember, scale is zero indexed, so 2 is the third value, and one less is the second). If you use this with a value, it will not have much effect, as it will be at one specific pixel value rather than a range like you want.

+mobile()

When the screen size is 400px (defined by rupture.mobile-cutoff) or less, the styles in the block will take effect.

+tablet()

When the screen size is between 1050px (defined by rupture.desktop-cutoff) and 400px (defined by mobile-cutoff), the styles in the block will take effect.

+desktop()

When the screen size is 1050px (defined by rupture.desktop-cutoff) or more, the styles in the block will take effect.

+hd()

When the screen size is 1800px (defined by rupture.hd-cutoff) or more, the styles in the block will take effect.

+density(value)

When the device has a pixel density of at least the given value, the styles in the block will take effect. The value should be a unitless pixel ratio number such as 1, 1.5, or 2. The value can also be retina, in which case the rupture.retina-density variable will be used.

+retina()

When the device has a pixel density of over rupture.retina-density (defaults to 1.5), the styles in the block will take effect.

+landscape()

When the viewport is wider than it is tall, the styles in the block will take effect. You can also pass orientation: landscape as a keyword argument to any of the other mixins except portrait():

+tablet(orientation: landscape)
+portrait()

When the viewport is taller than it is wide, the styles in the block will take effect. You can also pass orientation: portrait as a keyword argument to any of the other mixins except landscape():

+mobile(orientation: portrait)
+hover()

The hover media feature is used to query the user’s ability to hover over elements on the page with the primary pointing device.

PX to EM unit conversion

It is a popular opinion that using em units for media queries is a good practice, and for good reason.

Rupture allows you to automatically convert all your breakpoint units from px to em.

All you need to do to enable this behavior is to define an optional rupture.base-font-size (unless already defined) and set rupture.enable-em-breakpoints to true.

rupture.base-font-size defaults to 16px.

Example:

// base-font-size = 18px (commented out because it's optional and we want 16px)
rupture.enable-em-breakpoints = true

.some-ui-element
  width: 50%
  float: left
  +below(500px)
    width: 100%
    float: none

/**
 * compiles to:
 *
 * .some-ui-element {
 *   width: 50%;
 *   float: left;
 * }
 * @media screen and (max-width: 31.25em) {
 *   .some-ui-element {
 *     width: 100%;
 *     float: none;
 *   }
 * }
 */

Scale overlap

You can prevent scale slices from overlapping with neighbouring slices by setting the rupture.anti-overlap variable. This variable can contain a list of offset values for different units, which will be applied to your media queries so they do not overlap. The offset value(s) can be positive or negative, indicating how they should affect the media query arguments. If you provide a single value such as 1px but you are using em breakpoints, Rupture can convert the offset to the correct unit based on the rupture.base-font-size variable.

Alternatively, you can set rupture.anti-overlap to true or any falsy value, which are equivalent to 1px and 0px, respectively. Here are some examples:

rupture.anti-overlap = false // default value
rupture.anti-overlap = true // enables 1px (or em equivalent) overlap correction globally
rupture.anti-overlap = 0px // same as rupture.anti-overlap = false
rupture.anti-overlap = 1px // same as rupture.anti-overlap = true
rupture.anti-overlap = -1px // negative offsets decrease the `max-width` arguments
rupture.anti-overlap = 0.001em // positive offsets increase the `min-width` arguments
rupture.anti-overlap = 1px 0.0625em 0.0625rem // explicit relative values will be used if they are provided instead of calculating them from the font size

If you don't want to enable anti-overlapping globally, you can enable or disable it locally by passing the anti-overlap keyword argument to any of the mixins except retina(). This works exactly like the global rupture.anti-overlap variable, except you can specify it per mixin call. For example:

.overlap-force
  text-align center
  +at(2, anti-overlap: true)
    text-align right
  +at(3, anti-overlap: false)
    text-align left
  +from-width(4, anti-overlap: 1px)
    text-align justify
  +to-width(4, anti-overlap: 0.0625em)
    border 1px
  +from-width(5, anti-overlap: 0.0625rem)
    text-align justify
  +tablet(anti-overlap: 1px 0.0625em 0.0625rem)
    font-weight bold
  +mobile(anti-overlap: true)
    font-weight normal
  +desktop(anti-overlap: true)
    font-style italic
  +hd(anti-overlap: true)
    font-style oblique

The anti-overlap offset list may contain positive or negative values. Positive values will increase the media query's min-width argument, while negative values will decrease the max-width argument.

For example, with a positive offset:

rupture.scale = 0 400px 800px 1200px
rupture.anti-overlap = 1px

.some-ui-element
  text-align center
  +at(2)
    text-align right
  +at(3)
    text-align left

/**
  * compiles to:
  * .some-ui-element {
  *     text-align:center;
  * }
  * @media only screen and (min-width: 401px) and (max-width: 800px) {
  *     .some-ui-element {
  *         text-align:right;
  *     }
  * }
  * @media only screen and (min-width: 801px) and (max-width: 1200px) {
  *    .some-ui-element {
  *         text-align:left;
  *     }
  * }
  */

With a negative offset (and em breakpoints):

rupture.scale = 0 400px 800px 1200px
rupture.anti-overlap = -1px
rupture.enable-em-breakpoints = true

.some-ui-element
  text-align center
  +at(2)
    text-align right
  +at(3)
    text-align left

/**
 * compiles to:
 * .some-ui-element {
 *     text-align:center;
 * }
 * @media only screen and (min-width: 25em) and (max-width: 49.9375em) {
 *     .some-ui-element {
 *         text-align:right;
 *     }
 * }
 * @media only screen and (min-width: 50em) and (max-width: 74.9375em) {
 *     .some-ui-element {
 *         text-align:left;
 *     }
 * }
*/

More examples can be found in tests/overlap.styl.

Fallback Classes

Every Rupture mixin accepts an optional fallback-class argument that makes it easy to augment behavior for browsers that do not support media queries. For example the following code will produce the commented result below:

.ui-element
  color: blue
  +above(500px, fallback-class: '.lt-ie9')
    color: orange

/**
 * .ui-element {
 *   color: #00f;
 * }
 * @media screen and (min-width: 500px) {
 *   .ui-element {
 *     color: #ffa500;
 *   }
 * }
 * .lt-ie9 .ui-element {
 *   color: #ffa500;
 * }
 */

While this is a very straightforward way to create fallbacks, do note that the user would have to download the extra bytes of CSS even if they use a modern browser. If you want an alternative method that will enable you to serve separate stylesheets to those users, thus saving transferred bytes, please have a look at rupture.rasterise-media-queries

What is a "measure"?

When I say "measure" in any of the docs above, this could mean either pixels (like 500px), ems (like 20em), or an index on the scale (like 2). Scale indices will be converted from the index to whatever the value is at that index in the scale variable. The scale starts at a zero-index.

Usage

You can use this library in a couple different ways - I'll try to cover the most common here. First, if you are building your own stylus pipeline:

var stylus = require('stylus'),
    rupture = require('rupture');

stylus(fs.readFileSync('./example.styl', 'utf8'))
  .use(rupture())
  .render(function(err, css){
    if (err) return console.error(err);
    console.log(css);
  });

Second, you can use it when compiling stylus from the command line, if you install with npm install rupture -g. Not recommended, but some people like it this way.

$ stylus -u rupture -c example.styl

Finally, you might want to use it with express:

var express = require('express'),
    stylus = require('stylus'),
    rupture = require('rupture');

... etc ...

app.configure(function () {
  app.use(stylus.middleware({
    src: __dirname + '/views',
    dest: __dirname + '/public',
    compile: function (str, path, fn) {
      stylus(str)
        .set('filename', path)
        .use(rupture())
        .render(fn);
    }
  }));
});

... etc ...

This plugin is also be compatible with roots, the most awesome static compiler on the market (totally unbiased), although right now we are between releases, so I'll document it here once the next release is in beta at least.

Also, rupture automatically loads its mixins into all stylus files by default. If you'd like to prevent this and load it yourself with @import 'rupture' where you need it, you can pass {implict: false} to the main function when you execute it. It would look something like this:

.use(rupture({ implicit: false }))

Miscellaneous

Compatibility

Rupture is only compatible with stylus version 0.41.0 and up. If things are totally broken, check your stylus version and make sure you are up to date! This is especially true if you are experiencing errors with display: block - this is due to a bug in older versions of Stylus, and the only fix that isn't an ugly hack is to update to the latest version of Stylus.

License

Licensed under MIT, see license »

Contributing

See the contributing guide »