FUNctional CSS shorthand utilities. gr8
is both a handy set of functional css utilities, as well as a handy tool for generating functional css utilities.
npm i gr8
- super: Makes structuring layouts fast without imposing limitations
- handy: Utilities for columns, spacing, flexbox, typography, and more!
- flexible: Customize included utilities using options for breakpoints, spacing, units, etc...
- extensible: Add custom utilities using simple objects
- in-use: Folder Studio, 2Pac, Hardly Everything, LA Forum, Album Art IQ, Hassan Rahim, etc...
Skip straight to the utilities! 🏃
Let's set up gr8
with a couple options, add a custom text-color
utility, and append the styles to the head of our doc using the attach
method:
var gr8 = require('gr8')
var css = gr8({
spacing: [0, 1, 2, 4],
responsive: true
})
css.add({
prop: 'text-color',
vals: ['red', 'blue', 'green']
})
css.attach()
Now we can use the available css selectors in our app!
<div class="c6 p2 fs1-5 tcr" sm="c12 p1 tcb">subarashīdesu!</div>
gr8
default utilities:
column
.c1{width:8.333333333333332%}
.c2{width:16.666666666666664%}
.c3{width:25%}
.c4{width:33.33333333333333%}
.c5{width:41.66666666666667%}
.c6{width:50%}
.c7{width:58.333333333333336%}
.c8{width:66.66666666666666%}
.c9{width:75%}
.c10{width:83.33333333333334%}
.c11{width:91.66666666666666%}
.c12{width:100%}
.co0{margin-left:0}
.co1{margin-left:8.333333333333332%}
.co2{margin-left:16.666666666666664%}
.co3{margin-left:25%}
.co4{margin-left:33.33333333333333%}
.co5{margin-left:41.66666666666667%}
.co6{margin-left:50%}
.co7{margin-left:58.333333333333336%}
.co8{margin-left:66.66666666666666%}
.co9{margin-left:75%}
.co10{margin-left:83.33333333333334%}
.co11{margin-left:91.66666666666666%}
.co12{margin-left:100%}
Included Utilities: column.column
, column.offset
, column.nestedColumn
, column.nestedOffset
margin
.m0{margin:0}
.m1{margin:1rem}
.m2{margin:2rem}
.m3{margin:3rem}
.m4{margin:4rem}
.mt0{margin-top:0}
.mt1{margin-top:1rem}
.mt2{margin-top:2rem}
.mt3{margin-top:3rem}
.mt4{margin-top:4rem}
.mr0{margin-right:0}
.mr1{margin-right:1rem}
.mr2{margin-right:2rem}
.mr3{margin-right:3rem}
.mr4{margin-right:4rem}
.mb0{margin-bottom:0}
.mb1{margin-bottom:1rem}
.mb2{margin-bottom:2rem}
.mb3{margin-bottom:3rem}
.mb4{margin-bottom:4rem}
.ml0{margin-left:0}
.ml1{margin-left:1rem}
.ml2{margin-left:2rem}
.ml3{margin-left:3rem}
.ml4{margin-left:4rem}
.mx0{margin-left:0;margin-right:0}
.mx1{margin-left:1rem;margin-right:1rem}
.mx2{margin-left:2rem;margin-right:2rem}
.mx3{margin-left:3rem;margin-right:3rem}
.mx4{margin-left:4rem;margin-right:4rem}
.my0{margin-top:0;margin-bottom:0}
.my1{margin-top:1rem;margin-bottom:1rem}
.my2{margin-top:2rem;margin-bottom:2rem}
.my3{margin-top:3rem;margin-bottom:3rem}
.my4{margin-top:4rem;margin-bottom:4rem}
Included Utilities: margin.margin
, margin.marginX
, margin.marginY
padding
.p0{padding:0}
.p1{padding:1rem}
.p2{padding:2rem}
.p3{padding:3rem}
.p4{padding:4rem}
.pt0{padding-top:0}
.pt1{padding-top:1rem}
.pt2{padding-top:2rem}
.pt3{padding-top:3rem}
.pt4{padding-top:4rem}
.pr0{padding-right:0}
.pr1{padding-right:1rem}
.pr2{padding-right:2rem}
.pr3{padding-right:3rem}
.pr4{padding-right:4rem}
.pb0{padding-bottom:0}
.pb1{padding-bottom:1rem}
.pb2{padding-bottom:2rem}
.pb3{padding-bottom:3rem}
.pb4{padding-bottom:4rem}
.pl0{padding-left:0}
.pl1{padding-left:1rem}
.pl2{padding-left:2rem}
.pl3{padding-left:3rem}
.pl4{padding-left:4rem}
.px0{padding-left:0;padding-right:0}
.px1{padding-left:1rem;padding-right:1rem}
.px2{padding-left:2rem;padding-right:2rem}
.px3{padding-left:3rem;padding-right:3rem}
.px4{padding-left:4rem;padding-right:4rem}
.py0{padding-top:0;padding-bottom:0}
.py1{padding-top:1rem;padding-bottom:1rem}
.py2{padding-top:2rem;padding-bottom:2rem}
.py3{padding-top:3rem;padding-bottom:3rem}
.py4{padding-top:4rem;padding-bottom:4rem}
Included Utilities: padding.padding
, padding.paddingX
, padding.paddingY
opacity
.op0{opacity:0}
.op25{opacity:0.25}
.op50{opacity:0.5}
.op75{opacity:0.75}
.op100{opacity:1}
Included Utilities: opacity
background
.bgsc{background-size:cover}
.bgsct{background-size:contain}
.bgpc{background-position:center}
.bgpt{background-position:top}
.bgpr{background-position:right}
.bgpb{background-position:bottom}
.bgpl{background-position:left}
.bgrn{background-repeat:no-repeat}
.bgrx{background-repeat:repeat-x}
.bgry{background-repeat:repeat-y}
Included Utilities: background.size
, background.position
, background.repeat
flex
.x{display:flex}
.xac{align-items:center}
.xab{align-items:baseline}
.xas{align-items:stretch}
.xafs{align-items:flex-start}
.xafe{align-items:flex-end}
.xdr{flex-direction:row}
.xdrr{flex-direction:row-reverse}
.xdc{flex-direction:column}
.xdcr{flex-direction:column-reverse}
.xjc{justify-content:center}
.xjb{justify-content:space-between}
.xja{justify-content:space-around}
.xjs{justify-content:flex-start}
.xje{justify-content:flex-end}
.xw{flex-wrap:wrap}
.xwr{flex-wrap:wrap-reverse}
.xwn{flex-wrap:nowrap}
.xi{flex:initial}
.xx{flex:1}
.xa{flex:auto}
.xn{flex:none}
.xo0{order:0}
.xo1{order:1}
.xo2{order:2}
.xo3{order:3}
.xo4{order:4}
.xot{order:-1}
.xob{order:99}
Included Utilities: flex.display
, flex.align
, flex.direction
, flex.justify
, flex.wrap
, flex.flex
, flex.order
, flex.orderSpecial
display
.df{display:flex}
.db{display:block}
.dib{display:inline-block}
.di{display:inline}
.dt{display:table}
.dtc{display:table-cell}
.dtr{display:table-row}
.dn{display:none}
Included Utilities: display
float
.fl{float:left}
.fr{float:right}
.fn{float:none}
.cf:after{content:"";display:block;clear:both}
Included Utilities: float.float
, float.clear
overflow
.oh{overflow:hidden}
.os{overflow:scroll}
.oxh{overflow-x:hidden}
.oxs{overflow-x:scroll}
.oyh{overflow-y:hidden}
.oys{overflow-y:scroll}
Included Utilities: overflow
positioning
.psa{position:absolute}
.psr{position:relative}
.psf{position:fixed}
.pss{position:static}
.t0{top:0}
.r0{right:0}
.b0{bottom:0}
.l0{left:0}
.z0{z-index:0}
.z1{z-index:1}
.z2{z-index:2}
.z3{z-index:3}
.z4{z-index:4}
Included Utilities: positioning.position
, positioning.placement
, positioning.zindex
size
.w0{width:0}
.w100{width:100%}
.h0{height:0}
.h100{height:100%}
.vw100{width:100vw}
.vh100{height:100vh}
.vwmn100{min-width:100vw}
.vhmn100{min-height:100vh}
.vwmx100{max-width:100vw}
.vhmx100{max-height:100vh}
.ar0{content:"";display:block;padding-top:0}
.ar20{content:"";display:block;padding-top:20%}
.ar50{content:"";display:block;padding-top:50%}
.ar75{content:"";display:block;padding-top:75%}
.ar100{content:"";display:block;padding-top:100%}
Included Utilities: size.size
, size.viewportWidth
, size.viewportHeight
, size.viewportMinWidth
, size.viewportMinHeight
, size.viewportMaxWidth
, size.viewportMaxHeight
, size.aspect
typography
.fs6-4{font-size:6.4rem}
.fs3-2{font-size:3.2rem}
.fs2-4{font-size:2.4rem}
.fs1-6{font-size:1.6rem}
.fs1-2{font-size:1.2rem}
.fs1{font-size:1rem}
.lh1{line-height:1}
.lh1-5{line-height:1.5}
.fsn{font-style:normal}
.fsi{font-style:italic}
.fwn{font-weight:normal}
.fwb{font-weight:bold}
.tal{text-align:left}
.tac{text-align:center}
.tar{text-align:right}
.taj{text-align:justify}
.toi{text-overflow:initial}
.toc{text-overflow:clip}
.toe{text-overflow:ellipsis}
.tdu{text-decoration:underline}
.tdo{text-decoration:overline}
.tdlt{text-decoration:line-through}
.tdn{text-decoration:none}
.ttu{text-transform:uppercase}
.ttl{text-transform:lowercase}
.ttc{text-transform:capitalize}
.ttn{text-transform:none}
.vabl{vertical-align:baseline}
.vat{vertical-align:top}
.vam{vertical-align:middle}
.vab{vertical-align:bottom}
.wsn{white-space:normal}
.wsnw{white-space:nowrap}
.wsp{white-space:pre}
.wsi{white-space:inherit}
.tc1{columns:1}
.tc2{columns:2}
.tc3{columns:3}
.tc4{columns:4}
Included Utilities: type.fontSize
, type.lineHeight
, type.fontStyle
, type.fontWeight
, type.textAlign
, type.textOverflow
, type.textDecoration
, type.textTransform
, type.verticalAlign
, type.whiteSpace
, type.textColumn
miscellaneous
.curp{cursor:pointer}
.curd{cursor:default}
.cura{cursor:alias}
.curzi{cursor:zoom-in}
.curzo{cursor:zoom-out}
.usn{user-select:none}
.usa{user-select:auto}
.ust{user-select:text}
.pen{pointer-events:none}
.pea{pointer-events:auto}
Included Utilities: misc.cursor
, misc.userSelect
, misc.pointerEvents
development
.dev{outline:1px solid #912eff}
.dev > *{outline:1px solid #5497ff}
.dev > * > *{outline:1px solid #51feff}
.dev > * > * > *{outline:1px solid #ff0000}
.dev > * > * > * *{outline:1px solid #00ff00}
Included Utilities: dev
The gr8
api is very small and contains only 4 methods.
Initialize gr8
. View all available options.
Attach all utilities to the document head in a style tag. Returns style node.
Returns all utilities as a String
of css. Generally useful for writing css to a file.
Adds a gr8
utility. This is quite powerful so it gets its own section.
Removes a built-in gr8
utility. Accepts a single key or an array of keys.
Accepted values
column.column
, column.offset
, column.nestedColumn
, column.nestedOffset
, margin.margin
, margin.marginX
, margin.marginY
, padding.padding
, padding.paddingX
, padding.paddingY
, opacity
, background.size
, background.position
, background.repeat
, flex.display
, flex.align
, flex.direction
, flex.justify
, flex.wrap
, flex.flex
, flex.order
, flex.orderSpecial
, display
, float.float
, float.clear
, overflow
, positioning.position
, positioning.placement
, positioning.zindex
, size.size
, size.viewportWidth
, size.viewportHeight
, size.viewportMinWidth
, size.viewportMinHeight
, size.viewportMaxWidth
, size.viewportMaxHeight
, size.aspect
, type.fontSize
, type.lineHeight
, type.fontStyle
, type.fontWeight
, type.textAlign
, type.textOverflow
, type.textDecoration
, type.textTransform
, type.verticalAlign
, type.whiteSpace
, type.textColumn
, misc.cursor
, misc.userSelect
, misc.pointerEvents
, dev
Here are default options and details for what each option controls.
var css = gr8({
spacing: [0, 1, 2, 3, 4],
fontSize: [6.4, 3.2, 2.4, 1.6, 1.2, 1.0],
lineHeight: [1, 1.5],
size: [0, 100],
viewport: 100,
zIndex: [0, 1, 2, 3, 4],
order: [0, 1, 2, 3, 4],
opacity: [0, 25, 50, 75, 100],
aspect: [0, 20, 50, 75, 100],
textColumns: [1, 2, 3, 4],
unit: 'rem',
nested: false,
responsive: false,
attribute: true,
max: true,
breakpoints: {
xl: '1439px',
lg: '1260px',
md: '1023px',
sm: '767px'
}
})
option | expects | controls |
---|---|---|
spacing | Array /Number |
margin & padding utilities |
fontSize | Array /Number |
font-size utilities |
lineHeight | Array /Number |
line-height utilities |
size | Array /Number |
width & height utilities |
viewport | Array /Number |
viewport utilities |
zIndex | Array /Number |
zIndex utilities |
order | Array /Number |
flex-order utilities |
opacity | Array /Number |
opacity utilities |
aspect | Array /Number |
aspect ratio utilities |
textColumns | Array /Number |
text columns utilities |
unit | String |
default unit for numerical values |
nested | Bool |
support for nested columns |
responsive | Bool |
support for responsive utilities |
attribute | Bool |
breakpoint attribute selectors or prefixed class selectors? |
max | Bool |
max-width (desktop-first) or min-width (mobile-first) breakpoints? |
breakpoints | Object |
breakpoint keys and widths (only applies if using responsive utilities) |
The best way to learn how to write custom utilities is by peeking at the default utilities in src/utils!
Perhaps my favorite part about gr8
is adding custom utilities because it makes it simple to think about all your styles for a project in a functional manner. Utilities are added by passing options to the add
method. Let's take a look at creating a text-color
utility:
css.add({
prop: 'text-color',
vals: ['red', 'green', 'blue']
})
...creates these utilities:
.tcr{text-color:red}
.tcg{text-color:green}
.tcb{text-color:blue}
Estupendo!
Under the hood, gr8
tries to create sensible selectors using a combination of abbreviated css properties and values. We can also pass more options to the add
method for granular control:
css.add({
prefix: 'bdw',
suffix: ':after',
prop: 'border-width',
vals: {
sm: 1,
md: 4,
lg: 8
},
hyphenate: true,
unit: 'px',
transform: function (val) {
return val * 100
}
})
.bdw-sm:after{border-width:100px}
.bdw-md:after{border-width:400px}
.bdw-lg:after{border-width:800px}
While those specific utilities are not very useful, fancy utilities are possible by combining these options.
If the responsive option is set to true
, breakpoint attribute utilities will be generated:
@media (max-width: 1439px) {
[xl~="p0"]{padding:0}
[xl~="p1"]{padding:1rem}
[xl~="p2"]{padding:2rem}
[xl~="p3"]{padding:3rem}
[xl~="p4"]{padding:4rem}
/* etc... */
}
@media (max-width: 1260px) {
[lg~="p0"]{padding:0}
[lg~="p1"]{padding:1rem}
[lg~="p2"]{padding:2rem}
[lg~="p3"]{padding:3rem}
[lg~="p4"]{padding:4rem}
/* etc... */
}
@media (max-width: 1023px) {
[md~="p0"]{padding:0}
[md~="p1"]{padding:1rem}
[md~="p2"]{padding:2rem}
[md~="p3"]{padding:3rem}
[md~="p4"]{padding:4rem}
/* etc... */
}
@media (max-width: 767px) {
[sm~="p0"]{padding:0}
[sm~="p1"]{padding:1rem}
[sm~="p2"]{padding:2rem}
[sm~="p3"]{padding:3rem}
[sm~="p4"]{padding:4rem}
/* etc... */
}
Now utilities can be applied to elements per breakpoint:
<div class="p4" xl="p3" lg="p2" md="p1" sm="p0">My padding changes, groot!</div>
If breakpoint attributes aren't your style, you can set the attribute option to false
to use prefixed class selectors instead:
@media (max-width: 1439px) {
.xl-p0{padding:0}
/* etc... */
}
@media (max-width: 1260px) {
.lg-p0{padding:0}
/* etc... */
}
@media (max-width: 1023px) {
.md-p0{padding:0}
/* etc... */
}
@media (max-width: 767px) {
.sm-p0{padding:0}
/* etc... */
}
You can use min-width
instead of max-width
media queries by setting the max option to false
.
If the nested option is set to true
, utilities will be generated for column nesting:
.c1{width:8.333333333333332%}
.c1 .c1{width:100%}
.c2{width:16.666666666666664%}
.c2 .c1{width:50%}
.c2 .c2{width:100%}
.c3{width:25%}
.c3 .c1{width:33.33333333333333%}
.c3 .c2{width:66.66666666666666%}
.c3 .c3{width:100%}
/* etc... */
Now columns may be nested while retaining their actual size:
<div class="c1">
<div class="c1">I'm 100% of my parent!</div>
</div>
<div class="c2">
<div class="c1">I'm 50% of my parent!</div>
</div>
<div class="c3">
<div class="c1">I'm 33.333% of my parent!</div>
</div>
Warning: There are some specificity concerns when using nested columns in combination with responsive utilities. To minimize bloat 😳 not every possible cascade permutation is provided. You'll need to be a little redundant with your utilities to avoid issues, but it's quite doable.
The anatomy of gr8
utilities generally follow a simple and similar structure. For example:
.fs1-5{font-size:1.5rem}
┌─ prefix ┌─ property ┌─ unit
▼ ▼ ▼
.fs 1-5 { font-size : 1.5 rem }
▲ ▲
└─ selector value └─ value
prefix
is the shorthand identifier for what the utility does. Generally this will be an abbreviation of the respective css property (font-size
→fs
)selector value
is the specific identifier for the utility value. To make it css classname safe, decimals are replaced with hyphens (1.5
→1-5
)property
is the css property name which the utility targetsvalue
is the value corresponding to the css propertyunit
is the unit attached to the value
This structure is modified based on context and need of the utility. For example, the column utilities define the width property, but their suffix is a c
and the selector value is a numeric index rather than a sanitized version of the width value.
The attach
method is handy, especially during development, but for production you might want to css loaded in an external file, which is autoprefixed, minified, and maybe even purified (especially when using responsive utilities and nested columns!). The toString
method returns all the css as a simple string, and we can leverage this in a node script to save our css to a file:
var fs = require('fs')
var gr8 = require('gr8')
var css = gr8()
var cssString = css.toString()
fs.writeFile('gr8.css', cssString, function (err) {
if (err) {
return console.log(err)
}
console.log('gr8 saved to gr8.css!')
})
From there you may use whatever build process you like to get a nice, production-ready css file! Example of this is coming soon...
Work in progress...
f(css) is super and there are many solid approaches to functional css out there, gr8
just happens to be my personal take. I like a system which is very flexible. Many of the sites we make at Folder Studio would be quite tricky to pull off without quickly and easily adjusting utilities en masse.
Let me start by shouting out gravitons, basscss,
tachyons, and the like. These are all awesome tools, huge ups to their creators, and if you like 'em, use 'em. I've tried them all to varying degrees of success, I just happen to prefer gr8
style 🙃
gr8
is built and maintained by Jon Gacnik and used extensively in projects at Folder Studio.
Shout out Jon-Kyle Mohr for using gr8
for the past bits, totally tearing this thing apart and helping rebuild it in various past incarnations. This handy version is much thanks to him.
Subarashīdesu!