Skip to content

Instantly share code, notes, and snippets.

@geelen
Last active August 29, 2015 14:21
Show Gist options
  • Save geelen/3255bf7b48abad32c68d to your computer and use it in GitHub Desktop.
Save geelen/3255bf7b48abad32c68d to your computer and use it in GitHub Desktop.
Traits

Global traits, local components

The idea is to combine the best bit of global styling (reuse, small payload) and local styling (total isolation, first-class React syntax)

This is combined with the concept of traits: you can think of them as permitted property/value pairs. Instead of every component being able to have every CSS property available to it, you can reduce your permitted set to X font families, Y font-size + line-height pairs, Z foreground/background colour pairs, W padding amounts. This is based off my work using amcss on real projects — traits were the single key feature that kept me using AM.

The one-sentence explanation: A site defines a set of permitted visual features, all components are simply a combination of those features

Definitions

@define-trait X establishes X as a type of trait. These include the above mentioned concepts: typography, colouring, spacing, layout, etc.

Any CSS inside a @define-trait :default block are shared by all users of that class. If a trait was button, this would be all the default styling for a button.

Any sub-rule under a trait indicates a trait variant. These get turned into classes, and components mix them in. E.g. the variant vertical of trait flex gets turned into the class .t-trait--vertical.

Usage

:local exports local to React, which is a set of classnames that include an auto-generated class-name for any one-off CSS if needed, plus any traits it includes. Using that in React is dead simple.

Inside a :local expression, a & block will allow arbitrary CSS to be written. That will be extracted into a one-off class.

Using traits inside a :local block should be the main way you include styles. The syntax is like pure css: trait: variant-a variant-b;.

@define-trait flex {
:default {
display: flex;
}
inline {
display: inline-flex;
}
vertical {
flex-direction: column;
}
wrap {
flex-wrap: wrap;
}
align-center {
align-items: center;
}
align-stretch {
align-items: stretch;
}
/* more flex-parent aliases */
}
@define-trait flex-child {
no-shrink {
flex-shrink: 0;
}
grow {
flex-grow: 1;
}
/* more flex-child aliases */
}
@define-trait colors {
default {
background-color: white;
color: hsl(240,7%,29%);
}
inverted {
background-color: hsl(240,7%,29%);
color: hsl(240,7%,99%);
}
recessed {
background-color: #eee;
color: black;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
}
/* more types of colour pairings */
}
/* more traits */
:outer {
flex: vertical align-stretch;
colors: default;
}
:header {
colors: inverted;
flex-child: no-shrink;
& {
height: 10vh;
min-height: 100px;
/* more one-off styles */
}
}
import Styles from './my_component.css!'
export default class MyComponent extends React.Component {
render() {
return <div className={Styles.outer}>
<header className={Styles.header}>
<!-- more header stuff -->
</header>
<!-- more stuff here too -->
</div>
}
}
/* from definitions.css */
.t-flex {
display: flex;
}
.t-flex--inline {
display: inline-flex;
}
.t-flex--vertical {
flex-direction: column;
}
.t-flex--wrap {
flex-wrap: wrap;
}
.t-flex--align-center {
align-items: center;
}
.t-flex--align-stretch {
align-items: stretch;
}
.t-flex-child {
}
.t-flex-child--no-shrink {
flex-shrink: 0;
}
.t-flex-child--grow {
flex-grow: 1;
}
.t-colors {
}
.t-colors--default {
background-color: white;
color: hsl(240,7%,29%);
}
.t-colors--inverted {
background-color: hsl(240,7%,29%);
color: hsl(240,7%,99%);
}
.t-colors--recessed {
background-color: #eee;
color: black;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
}
/* from my_component.css */
.my_component_18ab4f_head {
height: 10vh;
min-height: 100px;
}
<div class="t-flex t-flex--vertical t-flex--align-stretch">
<header class="t-colors t-colors--inverted t-flex-child--no-shrink my_component_18ab4f_head">
<!-- more header stuff -->
</header>
<!-- more stuff here too -->
</div>

Questions for you, esteemed internet friend

  • Is there a better prefix that t- for generated styles? Something better as a separator than --?
  • Is Webpack's local .local[className] syntax better than :className? I don't see much point in conflating the idea of class and export. :local means "this is a pseudo-selector, it will be compiled into something". .local[className] seems dumb to me...
@geelen
Copy link
Author

geelen commented May 21, 2015

Oh, and on clashing. Only :root, :not and :host can appear at the beginning of a CSS selector. :not is the odd one out, since it takes an argument, but the semantics of :root and :host are very similar to an exported token — what they match is dependent on context.

Maybe :: would be better, since they're pseudo-elements not pseudo-_selectors, though you'd then have to avoid ::selection and potentially ::backdrop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment