Preamble¶
Welcome to ctr
, an open-source CSS framework designed to manage
the complexities of CSS. You've likely arrived here from
10xCSS, but if not, it's worth noting
that ctr
handles all the heavy lifting for 10xCSS CSS generation, and definitely worth checking out at
demo.10xCSS.com. Unfortunately, ctr
has been in a state of
suspended animation for quite some time, which is why I can't in good
faith recommend using it in production today. However, all the code examples
in this documentation are interactive, allowing you to experiment with ctr
without any installation or setup hassle.
Despite this documentation's age, the fundamental ideas and concepts outlined here remain unchanged, serving as both a blueprint and a glimpse into a future where you can seamlessly create, manage, and generate CSS and/or 10xCSS styles via a single declaration—alongside all the other goodies CSS preprocessors offer, or wish they did.
Notes
- I've used
ctr
daily for the past few years, and it's been pretty darn rock-solid; somewhat surprising given its B.T. (Before TypeScript) software—a barbaric era when variable type was determined through lunar phases and one's gut. - For an optimal experience, use Node
v10.24.1
; however, newer Node versions should also be compatible. There is a rare-ish edge case involving deeply nested states and their interactions with elements. In which, the state pseudo-class (:hover, :focus
) may be incorrectly applied to the component rather than the element. To be honest, I can't recall the exact specifics, but it's related to changes in the array sorting behavior in Nodev10.24.1+
and Immutable.jsv3.8.1
.
Synopsis¶
ctr
is a CSS framework, but it’s not comparable to a CSS framework like Bootstrap, Foundation, or similar ilk. However, ctr
is analogous to a CSS framework in that the primary goal is to expedite CSS development and reduce complexity. On many levels, the best comparison to draw upon is CSS preprocessors like Stylus, Less, Sass, and PostCSS. Many of the concepts preprocessors present such as variables, nesting, and mixins are concepts ctr
builds upon and around.
At the most basic level ctr
is an Object Oriented CSS framework built on Node.js that leverages Stylus to provide it with a familiar battle-tested CSS preprocessor environment. The data structure employed by ctr
is that of a key-value pair Object tree structure that mimics the Document Object Model. In other words, ctr
takes one of the most loved features in CSS preprocessors, nesting, and creates a logical and maintainable framework around the concept through Objects. This idea is reminiscent of Nicole Sullivan’s OOCSS architecture and Philip Walton’s OOCSS proposal. And through full-heartedly embracing “Objects” in both structure and application, ctr
leverages their inherent properties in a manner that is reusable, flexible, and extensible.
It’s through this Object structure that ctr
builds upon and incorporates the best of the best tools and concepts from the CSS preprocessor and Stylus community. This means you can hit the ground running and start composing your CSS styles right out of the box without wasting your precious time with setup costs. Furthermore, ctr
introduces new concepts like classes, common centralized style properties, and instance variables. Through the combined application of all the concepts, tools, and features that compose ctr
, it can cut CSS development time by 20-70%. But the cherry on top of the cake is that ctr
can be constructed in Stylus, Less, Javascript, and YAML. Granted, it may sound like I’m just throwing buzzwords around to sugar coat ctr
or wow you, but I don’t know how else to explain it.
A Simple Example¶
Let’s start your journey off with simplest of examples, a teal box.
HTML
<!-- html -->
<div class="blue-box"></div>
ctr('.blue-box', {
width: 200px
height: 200px
background: #3498db
})
.blue-box:
width: 200px
height: 200px
background: '#3498db'
.blue-box {
width: 200px;
height: 200px;
background: #3498db;
}
ctr('.blue-box', {
width: 200px
height: 200px
background: #3498db
})
.blue-box {
width: 200px;
height: 200px;
background: #3498db;
}
.blue-box:
width: 200px
height: 200px
background: '#3498db'
Result
Notes
- The
ctr
documentation is 100% interactive so go ahead and click theInteractive Examples
checkbox on the left and then click theEdit
button on this example in the bottom right-hand corner and change thewidth
to400px
- Alternatively, you can just click inside of the Stylus code and it will automatically go into edit-mode
- Interactive examples only work on screens larger than
770px
, sorry mobile users
A Simple Progression¶
Let’s turn up the volume a bit and make a simple button.
HTML
<!-- HTML -->
<div class="blue-button">
Button
</div>
ctr('.blue-button', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
})
.blue-button:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
.blue-button {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
ctr('.blue-button', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
})
.blue-button {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
.blue-button:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
Result
A Confidence Progression¶
Let’s turn up the volume a bit and compose a hover state background transition. This is where ctr
starts to shine since it configures all the hover state logic automatically.
HTML
<!-- HTML -->
<div class="blue-button-hover">
Button
</div>
ctr('.blue-button-hover', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
hover: {
color: #f1c40f
background: #9b59b6
}
})
.blue-button-hover:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
hover:
color: '#f1c40f'
background: '#9b59b6'
.blue-button-hover {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
.blue-button-hover:hover {
color: #f1c40f;
background: #9b59b6;
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: color, background;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
.blue-button-hover:not(:hover) {
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: color, background;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
ctr('.blue-button-hover', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
hover: {
color: #f1c40f
background: #9b59b6
}
})
.blue-button-hover {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
.blue-button-hover:hover {
color: #f1c40f;
background: #9b59b6;
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: color, background;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
.blue-button-hover:not(:hover) {
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: color, background;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
.blue-button-hover:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
hover:
color: '#f1c40f'
background: '#9b59b6'
Result
Notes
- Try adding another CSS property inside the state
hover
Object such asborder-radius: 8px
or change thehover
key into aactive
orfocus
key andctr
will configure everything for you - State documentation
A Fancy Progression¶
There is only one thing left to do — make it fancy.
HTML
<!-- HTML -->
<div class="blue-fancy-button">
Button
</div>
ctr('.blue-fancy-hover', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
hover: {
background: #9b59b6
transform: {
rotate: 360deg
scale: 2
}
animation: {
name: 'fancy'
count: infinite
delay: 0.5s
timeline: {
'50': {
color: #27ae60
}
}
}
}
})
.blue-fancy-hover:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
hover:
background: '#9b59b6'
transform:
rotate: 360deg
scale: 2
animation:
name: fancy
count: infinite
delay: 0.5s
timeline:
50:
color: '#27ae60'
.blue-fancy-hover {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
.blue-fancy-hover:hover {
background: #9b59b6;
animation-delay: 0.5s;
animation-name: fancy;
animation-duration: 0.5s;
transition-delay: 0s, 0s;
animation-fill-mode: none;
animation-direction: normal;
animation-play-state: running;
transition-duration: 0.5s, 0.5s;
animation-iteration-count: infinite;
transform: rotate(360deg) scale(2);
transition-property: background, transform;
animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
.blue-fancy-hover:not(:hover) {
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: background, transform;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
@keyframes fancy {
50% {
color: #27ae60;
}
}
ctr('.blue-fancy-hover', {
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: 0 50px
background: #3498db
line-height: 50px
text-align: center
display: inline-block
hover: {
background: #9b59b6
transform: {
rotate: 360deg
scale: 2
}
animation: {
name: 'fancy'
count: infinite
delay: 0.5s
timeline: {
'50': {
color: #27ae60
}
}
}
}
})
.blue-fancy-hover {
color: #fff;
height: 50px;
cursor: pointer;
font-size: 20px;
padding: 0 50px;
line-height: 50px;
text-align: center;
background: #3498db;
display: inline-block;
}
.blue-fancy-hover:hover {
background: #9b59b6;
animation-delay: 0.5s;
animation-name: fancy;
animation-duration: 0.5s;
transition-delay: 0s, 0s;
animation-fill-mode: none;
animation-direction: normal;
animation-play-state: running;
transition-duration: 0.5s, 0.5s;
animation-iteration-count: infinite;
transform: rotate(360deg) scale(2);
transition-property: background, transform;
animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
.blue-fancy-hover:not(:hover) {
transition-delay: 0s, 0s;
transition-duration: 0.5s, 0.5s;
transition-property: background, transform;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1), cubic-bezier(0.42, 0, 0.58, 1);
}
@keyframes fancy {
50% {
color: #27ae60;
}
}
.blue-fancy-hover:
color: white
height: 50px
font-size: 20px
cursor: pointer
padding: [0, 50px]
background: '#3498db'
line-height: 50px
text-align: center
display: inline-block
hover:
background: '#9b59b6'
transform:
rotate: 360deg
scale: 2
animation:
name: fancy
count: infinite
delay: 0.5s
timeline:
50:
color: '#27ae60'
Result
Notes
- Animation documentation
A Component Progression¶
Building upon the previous examples let’s combine the buttons into a button component of three buttons.
HTML
<!-- HTML -->
<div class="a-button-component">
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
</div>
ctr('.a-button-component', {
height: 200px
background: alpha(#3498db, 0.85)
border-radius: 4px
grid: {
container: true
align: 'center'
column: '1/1'
}
components: {
'*': {
color: #ecf0f1
height: 100px
background: alpha(#2ecc71, 0.75)
position: relative
before: {
alignSelf: 'center'
font-size: responsive 1em 3em
}
hover: {
// hover.css preset -> docs: state/hover.css/
preset: 'pulseGrow'
}
grid: {
column: '1/3'
}
}
'.one': {
before: {
content: 'one'
}
}
'.two': {
before: {
content: 'two'
}
}
'.three': {
before: {
content: 'three'
}
}
}
})
.a-button-component:
height: 200px
background: 'alpha(#3498db, 0.85)'
border-radius: 4px
grid:
container: true
align: center
column: 1/1
components:
'*':
color: '#ecf0f1'
height: 100px
background: 'alpha(#2ecc71, 0.75)'
position: relative
before:
alignSelf: center
font-size: [responsive, 1em, 3em]
hover:
# hover.css preset -> docs: state/hover.css/
preset: pulseGrow
grid:
column: 1/3
.one:
before:
content: one
.two:
before:
content: two
.three:
before:
content: three
.a-button-component {
display: flex;
height: 200px;
flex: 0 0 auto;
border-radius: 4px;
align-items: center;
flex-flow: row wrap;
justify-content: center;
background: rgba(52,152,219,0.85);
width: calc(99.9% * 1 / 1 - (30px - 30px * 1 / 1));
}
.a-button-component > * {
height: 100px;
color: #ecf0f1;
flex: 0 0 auto;
position: relative;
background: rgba(46,204,113,0.75);
width: calc(99.9% * 1 / 3 - (30px - 30px * 1 / 3));
}
.a-button-component:last-child {
margin-right: 0;
}
.a-button-component:nth-child(1n) {
float: right;
margin-right: 0;
}
.a-button-component > *::before {
top: 50%;
left: 50%;
right: auto;
bottom: auto;
position: absolute;
transform: translate(-50%, -50%);
font-size: calc(1em + 2 * ((100vw - 25em) / 87.5));
}
.a-button-component > *:hover {
animation-delay: 0s;
display: inline-block;
vertical-align: middle;
animation-duration: 0.3s;
transform: translateZ(0);
animation-fill-mode: none;
backface-visibility: hidden;
animation-play-state: running;
animation-direction: alternate;
animation-name: hvr-pulse-grow;
animation-timing-function: linear;
osx-font-smoothing: grayscale;
animation-iteration-count: infinite;
box-shadow: 0 0 1px rgba(0,0,0,0);
}
.a-button-component > *:not(:hover) {
display: inline-block;
vertical-align: middle;
transform: translateZ(0);
backface-visibility: hidden;
osx-font-smoothing: grayscale;
box-shadow: 0 0 1px rgba(0,0,0,0);
}
.a-button-component > .one::before {
content: "one";
}
.a-button-component > .two::before {
content: "two";
}
.a-button-component > .three::before {
content: "three";
}
.a-button-component > *:nth-child(1n) {
margin-right: 30px;
}
.a-button-component > *:last-child {
margin-right: 0;
}
.a-button-component > *:nth-child(3n) {
float: right;
margin-right: 0;
}
@media only screen and (max-width: 400px) {
.a-button-component > *::before {
font-size: 1em;
}
}
@media only screen and (min-width: 1800px) {
.a-button-component > *::before {
font-size: 3em;
}
}
@keyframes hvr-pulse-grow {
to {
transform: scale(1.1);
}
}
ctr('.a-button-component', {
height: 200px
background: alpha(#3498db, 0.85)
border-radius: 4px
grid: {
container: true
align: 'center'
column: '1/1'
}
components: {
'*': {
color: #ecf0f1
height: 100px
background: alpha(#2ecc71, 0.75)
position: relative
before: {
alignSelf: 'center'
font-size: responsive 1em 3em
}
hover: {
// hover.css preset -> docs: state/hover.css/
preset: 'pulseGrow'
}
grid: {
column: '1/3'
}
}
'.one': {
before: {
content: 'one'
}
}
'.two': {
before: {
content: 'two'
}
}
'.three': {
before: {
content: 'three'
}
}
}
})
.a-button-component {
display: flex;
height: 200px;
flex: 0 0 auto;
border-radius: 4px;
align-items: center;
flex-flow: row wrap;
justify-content: center;
background: rgba(52,152,219,0.85);
width: calc(99.9% * 1 / 1 - (30px - 30px * 1 / 1));
}
.a-button-component > * {
height: 100px;
color: #ecf0f1;
flex: 0 0 auto;
position: relative;
background: rgba(46,204,113,0.75);
width: calc(99.9% * 1 / 3 - (30px - 30px * 1 / 3));
}
.a-button-component:last-child {
margin-right: 0;
}
.a-button-component:nth-child(1n) {
float: right;
margin-right: 0;
}
.a-button-component > *::before {
top: 50%;
left: 50%;
right: auto;
bottom: auto;
position: absolute;
transform: translate(-50%, -50%);
font-size: calc(1em + 2 * ((100vw - 25em) / 87.5));
}
.a-button-component > *:hover {
animation-delay: 0s;
display: inline-block;
vertical-align: middle;
animation-duration: 0.3s;
transform: translateZ(0);
animation-fill-mode: none;
backface-visibility: hidden;
animation-play-state: running;
animation-direction: alternate;
animation-name: hvr-pulse-grow;
animation-timing-function: linear;
-moz-osx-font-smoothing: grayscale;
animation-iteration-count: infinite;
box-shadow: 0 0 1px rgba(0,0,0,0);
}
.a-button-component > *:not(:hover) {
display: inline-block;
vertical-align: middle;
transform: translateZ(0);
backface-visibility: hidden;
-moz-osx-font-smoothing: grayscale;
box-shadow: 0 0 1px rgba(0,0,0,0);
}
.a-button-component > .one::before {
content: "one";
}
.a-button-component > .two::before {
content: "two";
}
.a-button-component > .three::before {
content: "three";
}
.a-button-component > *:nth-child(1n) {
margin-right: 30px;
}
.a-button-component > *:last-child {
margin-right: 0;
}
.a-button-component > *:nth-child(3n) {
float: right;
margin-right: 0;
}
@media only screen and (max-width: 400px) {
.a-button-component > *::before {
font-size: 1em;
}
}
@media only screen and (min-width: 1800px) {
.a-button-component > *::before {
font-size: 3em;
}
}
@keyframes hvr-pulse-grow {
to {
transform: scale(1.1);
}
}
.a-button-component:
height: 200px
background: 'alpha(#3498db, 0.85)'
border-radius: 4px
grid:
container: true
align: center
column: 1/1
components:
'*':
color: '#ecf0f1'
height: 100px
background: 'alpha(#2ecc71, 0.75)'
position: relative
before:
alignSelf: center
font-size: [responsive, 1em, 3em]
hover:
# hover.css preset -> docs: state/hover.css/
preset: pulseGrow
grid:
column: 1/3
.one:
before:
content: one
.two:
before:
content: two
.three:
before:
content: three
Result
Notes
- The resulting CSS output is too big to display in whole, so if you want to see the rest of the CSS click the “Interactive Examples” checkbox on the left
- At face value this example might look confusing but the point is to demonstrate that ctr can compose any CSS logic, and it does so in a manner that significantly reduces development time
ctr
code count:44
lines and718
characters- Resulting CSS output:
93
lines and2050
characters
- Component documentation
Syntax¶
The Stylus syntax for ctr
is best described as a hybrid between Javascript and YAML. If you’re unfamiliar with the basic data structure of an Object, it would be in your best interest to hit pause now and do a bit of learning. Nevertheless, before you go any further, there are two critically important syntax rules you must abide by in a Stylus ctr
instance.
- Never use semicolons -
;
: Forget about them, do not even think about them, they do nothing but cause sorrow, pain, and errors in the Stylus parser. - Never use commas -
,
: In Stylus there is no need to separate Object Literals like you do with Javascript.
The syntax may feel a bit weird at first, especially if you do a fair amount of Object play in Javascript. Even if you despise the syntax at first, I recommend you give Stylus an honest run before you decide to ditch it in favor for the YAML or Javascript implementation.
Invocation¶
In Stylus, there are two ways to invoke a ctr
instance, declaratively or imperatively. Each achieves the same results, although, depending on your use-case or style preference, one may prove to be a bit more advantageous than the other.
// Stylus -> imperative method
<selecor#>
ctr({
<key>: <value>
})
// Stylus -> declarative method
ctr('<selecor#>', {
<key>: <value>
})
/*CSS -> Same output for both*/
<selecor#> {
<key>: <value>
}
Notes
- By default
ctr
orderes the output CSS via the combined length of the CSS property and its value although this option can be altered
Declarative Invocation¶
The declarative invocation is the recommended invocation method. The syntax is clear and concise, plus it forces conformity to the ctr
paradigm. However, more importantly, it offers complete control to handle any CSS situation under the sun, and the Stylus API mimics that of the Javascript API.
ctr('.aClass', {
width: 200px
height: 200px
})
ctr('#aId', {
width: 200px
height: 200px
background: red
})
ctr('#aId span', {
width: 200px
font-size: 1em
})
.aClass:
width: 200px
height: 200px
'#aId':
width: 200px
height: 200px
background: red
'#aId span':
width: 200px
font-size: 1em
.aClass {
width: 200px;
height: 200px;
}
#aId {
width: 200px;
height: 200px;
background: #f00;
}
#aId span {
width: 200px;
font-size: 1em;
}
ctr('.aClass', {
width: 200px
height: 200px
})
ctr('#aId', {
width: 200px
height: 200px
background: red
})
ctr('#aId span', {
width: 200px
font-size: 1em
})
.aClass {
width: 200px;
height: 200px;
}
#aId {
width: 200px;
height: 200px;
background: #f00;
}
#aId span {
width: 200px;
font-size: 1em;
}
.aClass:
width: 200px
height: 200px
'#aId':
width: 200px
height: 200px
background: red
'#aId span':
width: 200px
font-size: 1em
Notes
- In all the examples I use the declarative invocation
- The experimental Less plugin uses this syntax, and you can read more about it over at the Less docs
Imperative Invocation¶
The imperative invocation shines if you don’t want to drink all the ctr
kool-aid or only intend to use it for specific features. For example, let’s say you only want to use ctr
for animations, the imperative invocation method tailors to this use case because you can use it inline with the rest of your Stylus styles.
.aClass
ctr({
width: 200px
height: 200px
})
#aId
ctr({
width: 200px
height: 200px
background: red
})
span
ctr({
width: 200px
font-size: 1em
})
.aClass {
width: 200px;
height: 200px;
}
#aId {
width: 200px;
height: 200px;
background: #f00;
}
#aId span {
width: 200px;
font-size: 1em;
}
.aClass
ctr({
width: 200px
height: 200px
})
#aId
ctr({
width: 200px
height: 200px
background: red
})
span
ctr({
width: 200px
font-size: 1em
})
.aClass {
width: 200px;
height: 200px;
}
#aId {
width: 200px;
height: 200px;
background: #f00;
}
#aId span {
width: 200px;
font-size: 1em;
}
Notes
- Only works in Stylus
- I recommend you not use this invocation unless it’s warranted; that is to say, if you can construct your
ctr
instance using the declarative invocation don’t use the imperative invocation - Due to the limiting nature of the browser the imperative method does not always compile correctly
- I should note that the imperative method works flawlessly in node or at least I have yet to run into any issues