Instead of "breakpoints", we'll use different name: "ranges". "Breakpoint" represents a single point and we think it's easier to reason about responsiveness using "range".
Range
represents single range of resolutions.
Examples of ranges:
xs
- a range representing vertical mobile phones (from 0 to 419px)md
- a range representing standard vertical tablets (from 960px to 1200px)xl
- a range representing desktops (from 1600px to infinity)
let xs = new Range("xs", 0, 419); // a range named "xs" spanning from 0 to 419.
xs.from // 0
xs.to // 419
xs.name // "xs"
xs.isInfinite // false, this range is finite
xs.isCurrent // true if window.innerWidth is between 0 and 419, otherwise false
// Ranges can be infinite
let xl = new Range("xl", 1600, undefined); // a range named "xl" spanning from 1600 to infinity.
xl.from // 1600
xl.to // undefined
xl.isInfinite // true
Range
object has helper methods for CSS-in-JS:
// `mediaQuery` method generates media query string from `Range`
xs.mediaQuery // "(max-width: 419px)"
xl.mediaQuery // "(min-width: 1600px)"
let md = new Range("md", 720, 1280);
md.mediaQuery // "(min-width: 720px) and (max-width: 1280px)"
// `css` method generates CSS for specific range
xs.css('font-size: 10px;') // "@media (max-width: 419px) { font-size: 10px; }"
md.css(`
font-size: 10px;
max-width: 200px;
`)
// @media (min-width: 720px) and (max-width: 1280px) {
// font-size: 10px;
// max-width: 200px;
// }
Usually (in 99% of cases), you don't create Range
instances directly (via new Range
) in your project.
What you should do is to define entire range set that covers all resolutions from 0 to infinity. This is what RangeSet
class is for.
RangeSet
represents a resolution spectrum divided into ranges. You can create RangeSet
like this:
let rangeSet = new RangeSet({
xs: 0,
sm: 420,
md: 992,
lg: 1400,
xl: 1920
}
Here we created RangeSet
with 5 ranges: xs
, sm
, md
, lg
, xl
.
You can access specific Range
from RangeSet
in 2 ways:
// Via get` method
rangeSet.get('xs').from // 0
rangeSet.get('xs').to // 419
// Directly as a property
rangeSet.md.from // 992
rangeSet.md.to // 1399
RangeSet
has couple of helpers allowing creating new ranges based on your set:
// `to` method
rangeSet.to('md') // returns new `Range` spanning from 0 to 1399
rangeSet.to('lg') // returns new `Range` spanning from 0 to 1919
// `from` method
rangeSet.from('md') // return new `Range` spanning from 992 to infinity
rangeSet.from('lg') // return new `Range` spanning from 1400 to infinity
// `fromTo` method
rangeSet.fromTo('md', 'lg') // return new `Range` spanning from 992 to 1919
rangeSet.fromTo('xs', 'sm') // return new `Range` spanning from 0 to 991
There are other helper methods for RangeSet
:
rangeSet.current; // return currently active `Range` (based on current window.innerWidth value)
rangeSet.first // returns lowest `Range` (xs)
rangeSet.second // return second lowest `Range` (sm)
rangeSet.last // returns highest `Range` (xl)
You should always have one major RangeSet
in your project and assign it to RangeSet.main
property.
RangeSet.main
has special meaning and is used by other helpers functions (like rslin
).
By default, RangeSet.main
has following ranges:
{
xs: 0, // all phones in vertical mode are below this. Minimal 320px for iPhone SE, maximal 414px for iPhone 6+. All Galaxy Note etc, have lower ones.
xs_plus: 420, // horizontal phones + untypical small tablets (like Galaxy Nexus), least important resolution.
sm: 720, // all vertical tablets start with 720px (Surface). iPads and Galaxy Tab, etc, all are above. Nexus 7 is an exception, should behave as SM.
md: 960, // standard horizontal tablets are > 960px (even with Nexus 7), like iPad or Galaxy
lg: 1200, // smaller laptops (1280) and big tablets in horizontal mode (iPad Pro, Galaxy 10)
lg_plus: 1366, // most laptops 13 inch and 15 inch (1366, 1440px)
xl: 1600, // bigger resolution laptops (1600)
xl_plus: 2000 // desktops bigger than full HD
}
If you want to change them, in some init place of your project you should run sth like this:
RangeSet.main = new RangeSet({
xs: 0,
sm: 420,
md: 992,
lg: 1400,
xl: 1920
});
Very often it's easier to think about values as a function of resolution, not just a single value.
ResponsiveSize
is a class that represents a CSS value as a function (for all resolutions).
Imagine we have a container which width is 90vw
for resolutions below 1280px, 80vw
between 1280 and 1600px and has fixed width (1280px) from 1600px up.
Normally we use media queries to code this in CSS. However, we might think that container width is actually a function of resolution.
let containerWidth = new ResponsiveSize({
0: "80vw",
1280: "90vw",
1600: "1280px"
});
Now in your CSS-in-JS you can do sth like this.
// we use styled-components format
let Container = styled.div`
margin: 0 auto;
${containerWidth.css('width')}
`;
Voila. This will automatically translate to:
margin: 0 auto;
@media (max-width: 1279px) {
width: 80vw;
} @media (min-width: 1280px) and (max-width: 1599px) {
width: 90vw;
} @media (min-width: 1600px) {
width: 1280px;
}
If RangeSet.main
is set, you can also use range names instead of resolutions:
let responsiveSize = new ResponsiveSize({
xs: "80vw",
lg: "90vw",
xl: "1280px"
});
There's a useful shorthand for creating responsive sizes:
let responsiveSize = rs({
xs: "80vw",
lg: "90vw",
xl: "1280px"
});
let responsiveSize2 = rs(100); // constant value 100
let responsiveSize3 = rs("80vw"); // constant value "80vw"
let responsiveSize4 = rs(responsiveSize); // might also clone `ResponsiveSize`
It's useful when you create a React Component which takes some size as a prop. Let's say we create Container
with configurable width:
// styled-components format again
let Container = styled.div`
margin: 0 auto;
${props => rs(props.width).css('width')}
`;
// You can now pass width as a standard single value
let container1 = <Container width={100} />;
// But also you can use responsive size
let container2 = <Container width={{
xs: 100,
md: 200,
xl: 300
}} />;
Such API is very nice to write and read.
rslin
is a helper function that creates linear ResponsiveSize
.
Examples:
let linearSize = rslin(10, 50);
// Now `linearSize` has following value:
// - from 0 to RangeSet.main.second.from: 10px
// - from RangeSet.main.second.from to RangeSet.main.last.to: linear function from 10px to 50px
// - from RangeSert.main.last.to to infinity: 50px;
`;
Linear sizing is great for handling responsiveness of font sizes and vertical spacings. Calling linearSize.css('font-size')
returns:
@media (max-width: 419px) {
font-size: 10px;
}@media (min-width: 420px) and (max-width: 1919px) {
font-size: calc(0.02666666666666667*100vw + -1.200000000000001px);
}@media (min-width: 1920px) {
font-size: 50px;
}
This CSS is complicated but works and you don't have to worry about it. rslin
takes care of this.
IMPORTANT
In order to make it work, RangeSet.main
must be defined.
This should be pretty clear because rslin
takes 2 parameters and should know which resolutions are mapped to these values. First value is always mapped to RangeSet.main.first.to
(mobile phone resolution usually) and second one to RangeSet.main.last.from
(big desktop).