├── docs ├── logo.png ├── favicon.png ├── breakpoints.png ├── suchborder.gif └── index.html ├── package.json ├── LICENSE ├── css-media-vars.css └── README.md /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propjockey/css-media-vars/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propjockey/css-media-vars/HEAD/docs/favicon.png -------------------------------------------------------------------------------- /docs/breakpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propjockey/css-media-vars/HEAD/docs/breakpoints.png -------------------------------------------------------------------------------- /docs/suchborder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propjockey/css-media-vars/HEAD/docs/suchborder.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-media-vars", 3 | "version": "1.1.0", 4 | "description": "A brand new way to write responsive CSS. Named breakpoints, DRY selectors, no scripts, no builds, vanilla CSS.", 5 | "main": "css-media-vars.css", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/propjockey/css-media-vars.git" 12 | }, 13 | "keywords": [ 14 | "css", 15 | "breakpoints", 16 | "media queries" 17 | ], 18 | "author": "James0x57", 19 | "license": "BSD-2-Clause", 20 | "bugs": { 21 | "url": "https://github.com/propjockey/css-media-vars/issues" 22 | }, 23 | "homepage": "https://github.com/propjockey/css-media-vars#readme" 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) James0x57, PropJockey, 2020 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /css-media-vars.css: -------------------------------------------------------------------------------- 1 | /** 2 | * css-media-vars 3 | * BSD 2-Clause License 4 | * Copyright (c) PropJockey, 2021 5 | */ 6 | 7 | html { 8 | --media-print: initial; 9 | --media-screen: initial; 10 | --media-speech: initial; 11 | --media-xs: initial; 12 | --media-sm: initial; 13 | --media-md: initial; 14 | --media-lg: initial; 15 | --media-xl: initial; 16 | --media-gte-sm: initial; 17 | --media-gte-md: initial; 18 | --media-gte-lg: initial; 19 | --media-lte-sm: initial; 20 | --media-lte-md: initial; 21 | --media-lte-lg: initial; 22 | --media-sm-md: initial; 23 | --media-sm-md-lg: initial; 24 | --media-md-lg: initial; 25 | --media-landscape: initial; 26 | --media-portrait: initial; 27 | --media-prefers-light: initial; 28 | --media-prefers-dark: initial; 29 | --media-prefers-reduced-motion: initial; 30 | --media-any-hover-hover: initial; 31 | --media-any-hover-none: initial; 32 | --media-any-pointer-coarse: initial; 33 | --media-any-pointer-fine: initial; 34 | --media-any-pointer-none: initial; 35 | --media-hover-hover: initial; 36 | --media-hover-none: initial; 37 | --media-pointer-coarse: initial; 38 | --media-pointer-fine: initial; 39 | --media-pointer-none: initial; 40 | } 41 | @property --media-__atPropCatchSpace { syntax: ""; initial-value: 0; inherits: false; } 42 | html { 43 | --media-__atPropCatchSpace: ; 44 | --media-at-property-unsupported: var(--media-__atPropCatchSpace, ); 45 | } 46 | @media print { 47 | html { --media-print: ; } 48 | } 49 | @media screen { 50 | html { --media-screen: ; } 51 | } 52 | @media speech { 53 | html { --media-speech: ; } 54 | } 55 | @media (max-width: 37.499em) { 56 | html { 57 | --media-xs: ; 58 | --media-lte-sm: ; 59 | --media-lte-md: ; 60 | --media-lte-lg: ; 61 | } 62 | } 63 | @media (min-width: 37.5em) and (max-width: 56.249em) { 64 | html { 65 | --media-sm: ; 66 | --media-gte-sm: ; 67 | --media-lte-sm: ; 68 | --media-lte-md: ; 69 | --media-lte-lg: ; 70 | --media-sm-md: ; 71 | --media-sm-md-lg: ; 72 | } 73 | } 74 | @media (min-width: 56.25em) and (max-width: 74.99em) { 75 | html { 76 | --media-md: ; 77 | --media-gte-sm: ; 78 | --media-gte-md: ; 79 | --media-lte-md: ; 80 | --media-lte-lg: ; 81 | --media-sm-md: ; 82 | --media-sm-md-lg: ; 83 | --media-md-lg: ; 84 | } 85 | } 86 | @media (min-width: 75em) and (max-width: 112.499em) { 87 | html { 88 | --media-lg: ; 89 | --media-gte-sm: ; 90 | --media-gte-md: ; 91 | --media-gte-lg: ; 92 | --media-lte-lg: ; 93 | --media-sm-md-lg: ; 94 | --media-md-lg: ; 95 | } 96 | } 97 | @media (min-width: 112.5em) { 98 | html { 99 | --media-xl: ; 100 | --media-gte-sm: ; 101 | --media-gte-md: ; 102 | --media-gte-lg: ; 103 | } 104 | } 105 | @media (prefers-color-scheme: light) { 106 | html { 107 | --media-prefers-light: ; 108 | } 109 | } 110 | @media (prefers-color-scheme: dark) { 111 | html { 112 | --media-prefers-dark: ; 113 | } 114 | } 115 | @media (any-hover: hover) { 116 | html { 117 | --media-any-hover-hover: ; 118 | } 119 | } 120 | @media (any-hover: none) { 121 | html { 122 | --media-any-hover-none: ; 123 | } 124 | } 125 | @media (any-pointer: coarse) { 126 | html { 127 | --media-any-pointer-coarse: ; 128 | } 129 | } 130 | @media (any-pointer: fine) { 131 | html { 132 | --media-any-pointer-fine: ; 133 | } 134 | } 135 | @media (any-pointer: none) { 136 | html { 137 | --media-any-pointer-none: ; 138 | } 139 | } 140 | @media (hover: hover) { 141 | html { 142 | --media-hover-hover: ; 143 | } 144 | } 145 | @media (hover: none) { 146 | html { 147 | --media-hover-none: ; 148 | } 149 | } 150 | @media (pointer: coarse) { 151 | html { 152 | --media-pointer-coarse: ; 153 | } 154 | } 155 | @media (pointer: fine) { 156 | html { 157 | --media-pointer-fine: ; 158 | } 159 | } 160 | @media (pointer: none) { 161 | html { 162 | --media-pointer-none: ; 163 | } 164 | } 165 | @media (prefers-reduced-motion) { 166 | html { 167 | --media-prefers-reduced-motion: ; 168 | } 169 | } 170 | @media (orientation: landscape) { 171 | html { 172 | --media-landscape: ; 173 | } 174 | } 175 | @media (orientation: portrait) { 176 | html { 177 | --media-portrait: ; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![JaneOri](https://img.shields.io/badge/JaneOri%20%F0%9F%91%BD-I%20made%20a%20thing!-blueviolet.svg?labelColor=222222)](https://twitter.com/Jane0ri) 2 | 3 | # css-media-vars from PropJockey 4 | A brand new way to write responsive CSS. Named breakpoints, DRY selectors, no scripts, no builds, vanilla CSS. 5 | 6 | Docs+Demos at: https://propjockey.github.io/css-media-vars/ 7 | 8 | NPM: https://www.npmjs.com/package/css-media-vars 9 | 10 | GitHub: https://github.com/propjockey/css-media-vars 11 | 12 | Install: 13 | `$ npm install css-media-vars` 14 | Then include the `/node_modules/css-media-vars/css-media-vars.css` file before any stylesheets that use it. 15 | 16 | OR 17 | 18 | Use your favorite NPM CDN and include it on your page before other stylesheets for small projects. Like so: 19 | ```html 20 | 21 | ``` 22 | 23 | ## Example of what your mobile-first CSS would look like 24 | ```css 25 | .breakpoints-demo > * { 26 | --xs-width: var(--media-xs) 100%; 27 | --sm-width: var(--media-sm) 49%; 28 | --md-width: var(--media-md) 32%; 29 | --lg-width: var(--media-gte-lg) 24%; 30 | width: var(--xs-width, var(--sm-width, var(--md-width, var(--lg-width)))); 31 | 32 | --sm-and-down-bg: var(--media-lte-sm) red; 33 | --md-and-up-bg: var(--media-gte-md) green; 34 | background: var(--sm-and-down-bg, var(--md-and-up-bg)); 35 | } 36 | ``` 37 | 38 | VS Writing this same basic responsive CSS in the traditional way: 39 | ```css 40 | .breakpoints-demo > * { 41 | width: 100%; 42 | background: red; 43 | } 44 | @media (min-width: 37.5em) and (max-width: 56.249em) { 45 | .breakpoints-demo > * { 46 | width: 49%; 47 | } 48 | } 49 | @media (min-width: 56.25em) and (max-width: 74.99em) { 50 | .breakpoints-demo > * { 51 | width: 32%; 52 | } 53 | } 54 | @media (min-width: 56.25em) { 55 | .breakpoints-demo > * { 56 | background: green; 57 | } 58 | } 59 | @media (min-width: 75em) { 60 | .breakpoints-demo > * { 61 | width: 24%; 62 | } 63 | } 64 | ``` 65 | 66 | css-media-vars let you write responsive, vanilla, CSS with named breakpoints, DRY selectors, and easier to mantain code. 67 | 68 | ## New in v1.1.0 69 | Write CSS that responds to the browser's support of @property from Houdini: 70 | ```css 71 | .warning-at-property-unsupported { 72 | --display-block-if-unsupported: var(--media-at-property-unsupported) block; 73 | display: var(--display-block-if-unsupported, none); /* display: none if @property is supported */ 74 | } 75 | .congrats-at-property-supported { 76 | --display-none-if-unsupported: var(--media-at-property-unsupported) none; 77 | display: var(--display-none-if-unsupported, block); /* display: block if @property is supported */ 78 | } 79 | :root { 80 | --at-prop-unsupported-color: var(--media-at-property-unsupported) red; 81 | --at-prop-color: var(--at-prop-unsupported-color, green); 82 | /* var(--at-prop-color) will return: */ 83 | /* red if @property is unsupported */ 84 | /* green if @property is supported */ 85 | } 86 | ``` 87 | 88 | ## Minification 89 | Environments that force minification with PostCSS and cssnano (or other CSS minifiers) may produce invalid CSS by striping the space from a `--custom-var: ;` assignment. To avoid this with **Create React App**, copy `css-media-vars.css` into the public folder and manually include it in the index.html file to avoid the minification until they're fixed. 90 | 91 | You can read more about using the public folder here: https://create-react-app.dev/docs/using-the-public-folder/ 92 | 93 | ## Innovative Possibilities 94 | 95 | Responsive CSS written with css-media-vars is so DRY with selecotors, you can skip them entirely. 96 | 97 | Easily write responsive CSS inline, directly on the style attribute if you wish. No scripting, no @‍media nesting, just vanilla CSS with some hidden [space toggle](https://github.com/propjockey/css-sweeper#css-is-a-programming-language-thanks-to-the-space-toggle-trick) magic. 98 | 99 | [![gif of responsive css changing state based on CSS written inline with the style attribute](https://i.imgur.com/3RkpkEn.gif)](https://codepen.io/propjockey/pen/xxPZLBy?editors=1100) 100 | 101 | ### Hypothetical innovative example 102 | 103 | This may be especially useful for enhancing user generated content without the overhead traditional responsive CSS causes. For example, if your platform supports markdown, you could change the `---` hr separator to support user-supplied color variations that are resposive to light/dark mode: 104 | 105 | --- 106 | 107 | Right now, 108 | `---` 109 | becomes 110 | ```html 111 |
112 | ``` 113 | with global styles like: 114 | ```css 115 | hr { 116 | border: 1px solid #cccccc; 117 | } 118 | ``` 119 | 120 | --- 121 | 122 | Using css-media-vars to enhance this feature, you could transpile this 123 | 124 | `---#ff0000-#880000---` 125 | 126 | into this 127 | 128 | ```html 129 |
130 | ``` 131 | and update the global CSS to this: 132 | ```css 133 | hr { 134 | border: 1px solid var(--light, var(--dark, #cccccc));; 135 | } 136 | ``` 137 | 138 | --- 139 | 140 | To do this same progressive enhancement without css-media-vars, the easiest way looks like this: 141 | ```html 142 |
143 | ``` 144 | and update the global CSS to this: 145 | ```css 146 | hr { 147 | border: 1px solid #cccccc; 148 | } 149 | 150 | @media (prefers-color-scheme: light) { 151 | hr { 152 | border-color: var(--light, #cccccc); 153 | } 154 | } 155 | @media (prefers-color-scheme: dark) { 156 | hr { 157 | border-color: var(--dark, #cccccc); 158 | } 159 | } 160 | ``` 161 | 162 | --- 163 | 164 | You may not be sold from that single trade-off alone, but imagine you had even just a few more features you wanted to enhance in a similar way. 165 | 166 | Your global css becomes large very quickly, and depending on the complexity of your selecotrs (which would be repeated several times), your global CSS begins to lock in your DOM and make future element changes a tech-debt nightmare. 167 | 168 | You avoid all of that with css-media-vars. 🎉 169 | 170 | ## CHANGELOG: 171 | 172 | v1.1.0 - June 27th, 2021: 173 | * added `--media-at-property-unsupported` space toggle that detects CSS @property support 174 | 175 | v1.0.0 - July 16th, 2020: 176 | * Initial release 177 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | css-media-vars from PropJockey 7 | 8 | 9 | 10 | 11 | 17 | 107 | 108 | 109 |
110 | #

css-media-vars Documentation & Demos

111 |

112 | css-media-vars 113 | A brand new way to write responsive CSS. Named breakpoints, DRY selectors, no scripts, no builds, vanilla CSS. 114 |

115 |
116 |
117 | Resources:
118 | Docs+Demos | 119 | NPM | 120 | GitHub | 121 | Twitter 122 |
123 |
124 | 125 |
126 | #

Installing css-media-vars


127 |

128 | Install with NPM:
129 | $ npm install css-media-vars
130 | Then include the /node_modules/css-media-vars/css-media-vars.css file once, before any stylesheets that use it.
131 |
132 | OR
133 |
134 | Use your favorite NPM CDN and include it on your page before other stylesheets for small projects. Like so:
135 | <link rel="stylesheet" type="text/css" href="https://unpkg.com/css-media-vars/css-media-vars.css"> 136 |

137 |
138 | 139 |
140 | #

Basics of css-media-vars


141 |

142 | css-media-vars adds several --custom-css-variables, all namespaced with "--media-" and based on media queries, onto the HTML tag. These vars act as universal mixins for your CSS values. If the media query is true, your value will be used. If it's false, the var(..., fallback) will be used.
143 |
144 | For example, there are several named breakpoints that respond to the width of the screen, such as --media-lte-sm.
145 |
146 | In your CSS, if you want a value to only apply when the screen is less than or equal to the "small" breakpoint range, you mix it into your value:
147 | --my-small-value: var(--media-lte-sm) 2px;
148 | From this point, you can use your variable anywhere and it will only be "2px" if the media query is true. Else, it uses the fallback you provide, like so:
149 | border: var(--my-small-value, 15px) solid green;
150 | Border changing size at 900px breakpoint
151 | Here's a link to the jsbin pictured in the gif: https://jsbin.com/giqedowale/edit?css,output
152 |
153 | If you need multiple break points, all you have to do is mix a different breakpoint into another variable and use it in the first fallback:
154 | 155 | --my-small-value: var(--media-lte-sm) 2px;
156 | --my-medium-value: var(--media-md) 15px;
157 | border: var(--my-small-value, var(--my-medium-value, 30px)) solid green;
158 |

159 | In this case, because the small values are listed first, this approach is mobile-first! CSS Variables don't compile the fallback unless it's used (similar to the expected short-circuting of conditionals in JavaScript).
160 |
161 | This is all vanilla CSS. No JS or build step is necessary.
162 | The library is small and built on a CSS trick called "Space Toggle" discovered in the development of augmented-ui. You can read more about Space Toggle in these tweets which contain links to JSBins you can explore further with:
163 | Second Tweet, a simple demo
164 | First Tweet, an advanced demo
165 |
166 | Browser support for CSS Variables is currently 94% globally according to caniuse.
167 | Each of the --media-* vars are using the actual media query in CSS to switch them to the "true" state. Browser support depends on the feature but will always behave as if it's "false" if the media query isn't supported in the user's browser, such as --media-prefers-light which only recently gained traction. 168 |

169 |
170 | 171 |
172 | #

Named Breakpoints in css-media-vars


173 |

Available Breakpoint Ranges

174 |

175 | xs = 0px to 600px, (0em+)
176 | sm = 600px to 900px, (37.5em+)
177 | md = 900px to 1200px, (56.25em+)
178 | lg = 1200px to 1800px, (75em+)
179 | xl = 1800px and up, (112.5em+)
180 | Why these breakpoints? https://www.freecodecamp.org/news/the-100-correct-way-to-do-css-breakpoints-88d6a5ba1862/
181 | Why are they implemented as em units? https://zellwk.com/blog/media-query-units/
182 |
183 | Media vars available for these breakpoint ranges:
184 | --media-xs (only extra small: everything below 600px)
185 | --media-sm (only small: between 600px and 900px)
186 | --media-md (only medium: between 900px and 1200px)
187 | --media-lg (only large: 1200px to 1800px)
188 | --media-xl (only extra large: 1800px and above)
189 | gte (greater than or equal)
190 | --media-gte-sm (small and up: 600px and above)
191 | --media-gte-md (medium and up: 900px and above)
192 | --media-gte-lg (large and up: 1200px and above)
193 | lte (less than or equal)
194 | --media-lte-sm (small and down: everything below 900px)
195 | --media-lte-md (medium and down: everything below 1200px)
196 | --media-lte-lg (large and down: everything below 1800px)
197 | sets
198 | --media-sm-md (small and medium only: between 600px and 1200px)
199 | --media-sm-md-lg (small, medium, and large only: between 600px and 1800px)
200 | --media-md-lg (medium and large only: between 900px and 1800px)
201 |
202 | Here is a visual guide to see the ranges and how they work together more easily:
203 | Visualization for breakpoint ranges
204 |

205 | 206 |

Breakpoint Demo

207 | 227 | .breakpoints-demo
228 |
229 |
Follow
230 |
me
231 |
on
232 |
Twitter
233 |
@James0x57
234 |
235 |
236 | 237 |
238 | #

Print, Screen, and Speech


239 |

240 | --media-print
241 | --media-screen
242 | --media-speech
243 |

244 |
245 | 255 | .media-print-demo:
256 |
Preview Print!
257 |
258 |
259 | 260 |
261 | #

Other Implemented Media Vars


262 |

263 | --media-landscape
264 | --media-portrait
265 | --media-prefers-light
266 | --media-prefers-dark
267 | --media-prefers-reduced-motion
268 | --media-any-hover-hover
269 | --media-any-hover-none
270 | --media-any-pointer-coarse
271 | --media-any-pointer-fine
272 | --media-any-pointer-none
273 | --media-hover-hover
274 | --media-hover-none
275 | --media-pointer-coarse
276 | --media-pointer-fine
277 | --media-pointer-none
278 |

279 |
280 | 281 |
282 | #

Combine media queries


283 |

284 | combine using AND logic:
285 | --gray-if-prefers-dark-and-is-printing: var(--media-prefers-dark) var(--media-print) gray;
286 |
287 | combine using OR logic:
288 | --gray-if-prefers-dark-or-is-printing: var(--media-prefers-dark, var(--media-print)) gray;
289 |
290 | If the condition applies to multiple properties, you can create compound conditions and use them in multiple places:
291 | 292 | --prefers-dark-and-is-printing: var(--media-prefers-dark) var(--media-print);
293 | --my-query-bg-color: var(--prefers-dark-and-is-printing) gray;
294 | --my-query-border-size: var(--prefers-dark-and-is-printing) 2px;
295 | background: var(--my-query-bg-color, black);
296 | border: var(--my-query-border-size, 10px) solid black; 297 |
298 |

299 |
300 | 301 |
302 | #

Detect support of CSS @property (new in v1.1.0)


303 |

304 | --media-at-property-unsupported
305 |

306 |
307 | 325 | .at-prop-demo:
326 |
327 | Demonstration of @property support detection 328 |
This browser doesn't support @property yet :(
329 |
This browser DOES support @property :)
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 | Feel free to reach out on github or twitter with feedback, requests, or for help getting started!
340 |
341 |
342 |
343 |
344 | follow me on twitter for updates and other web stuff
345 |
346 |
347 |
348 |
349 |
350 | 351 | 352 | --------------------------------------------------------------------------------