Is it possible without the SPOT CSS framework?
I like the idea of SPOT CSS in how tidy the code is, but can't I use these principles without using a framework?
Yes, it is possible. But the code won't be cleaner and more understandable either.
Let's see the example code from the homepage rewrited without the SPOT CSS framework. And let's do it in SCSS syntax, because it's more common.
With the SPOT CSS framework:
- SASS
+component('.btn') +register +state(':hover') +variant('.primary') +context('.modal') +responsive('{<md}') +element('.icon') +pseudo-element('before') +element('.text') +default display: inline-block padding: 0.5em 1.5em background: $col-btn-bg line-height: 1.5 +state(':hover') background: $col-btn-bg-hover +variant('.primary') +default background: $col-btn-bg-prim +state(':hover') background: $col-btn-bg-hover-prim +context('.modal') display: block +responsive('{<md}') padding: 0.25em 1em +_____________________________ +element('.icon') +default display: inline-block width: 1.5em height: 1.5em opacity: 0.75 +state(':hover') transform: scale(1.15) +variant('.primary') opacity: 1 +context('.modal') opacity: 0.85 +_____________________________ +pseudo-element('before') +default content: '\0192' display: inline-block font-family: icons opacity: 0.8 +state(':hover') opacity: 1 +variant('.primary') text-shadow: 0 0 0.5em black +responsive('{<md}') transform: scale(1.25) +_____________________________ +element('.text') +default white-space: nowrap vertical-align: middle +variant('.primary') font-weight: bold +responsive('{<md}') white-space: normal
Without the SPOT CSS framework:
- SCSS
.btn { display: inline-block; padding: 0.5em 1.5em; background: $col-btn-bg; line-height: 1.5; &:hover { background: $col-btn-bg-hover; } &.primary { background: $col-btn-bg-prim; &:hover { background: $col-btn-bg-hover-prim; } } .modal & { display: block; } @include less-than(md) { padding: 0.25em 1em; } //_____________________________ .icon { display: inline-block; width: 1.5em; height: 1.5em; opacity: 0.75; @at-root .btn:hover .icon { transform: scale(1.15); } @at-root .btn.primary .icon { opacity: 1; } .modal & { opacity: 0.85; } //_____________________________ &::before { content: '\0192'; display: inline-block; font-family: icons; opacity: 0.8; @at-root .btn:hover .icon::before { opacity: 1; } @at-root .btn.primary .icon::before { text-shadow: 0 0 0.5em black; } @include less-than(md) { transform: scale(1.25); } } } //_____________________________ .text { white-space: nowrap; vertical-align: middle; @at-root .btn.primary .text { font-weight: bold; } @include less-than(md) { white-space: normal; } }}
Let's describe what happened above.
No register section
The register section is a specialty of the SPOT CSS framework. So without it we can omit it completely.
But it's a pity, because it provides a unique overview of the component, more in the documentation about +register mixin.
+component('.btn') +register +state(':hover') +variant('.primary') +context('.modal') +responsive('{<md}') +element('.icon') +pseudo-element('before') +element('.text') +default display: inline-block ...
The root element of the component
The root element of the component is simple and gives the impression that the whole component will be simple too. This is not true. The use of the &
symbol is usually only possible in the root element.
With the SPOT:
- SASS
...
+default
display: inline-block
padding: 0.5em 1.5em
background: $col-btn-bg
line-height: 1.5
+state(':hover')
background: $col-btn-bg-hover
+variant('.primary')
+default
background: $col-btn-bg-prim
+state(':hover')
background: $col-btn-bg-hover-prim
+context('.modal')
display: block
+responsive('{<md}')
padding: 0.25em 1em
...
Without the SPOT:
- SCSS
.btn {
display: inline-block;
padding: 0.5em 1.5em;
background: $col-btn-bg;
line-height: 1.5;
&:hover {
background: $col-btn-bg-hover;
}
&.primary {
background: $col-btn-bg-prim;
&:hover {
background: $col-btn-bg-hover-prim;
}
}
.modal & {
display: block;
}
@media (max-width: 720px) {
padding: 0.25em 1em;
}
...
The +default section
We don't need to mark the "default" section at all, it is always the first one, so we just omit the use of mixin +default. This is easy.
... +default display: inline-block; padding: 0.5em 1.5em; background: $col-btn-bg; line-height: 1.5;...
States and variants in the root element
This is straightforward, just use the &
symbol.
With the SPOT:
- SASS
...
+state(':hover')
background: $col-btn-bg-hover
+variant('.primary')
+default
background: $col-btn-bg-prim
+state(':hover')
background: $col-btn-bg-hover-prim
...
Without the SPOT:
- SCSS
...
&:hover {
background: $col-btn-bg-hover;
}
&.primary {
background: $col-btn-bg-prim;
&:hover {
background: $col-btn-bg-hover-prim;
}
}
...
Context in the root element
This is very straightforward too.
With the SPOT:
- SASS
...
+context('.modal')
display: block
...
Without the SPOT:
- SCSS
...
.modal & {
display: block;
}
...
Responsiveness in the root element
This is very straightforward as well.
With the SPOT:
- SASS
...
+responsive('{<md}')
padding: 0.25em 1em
...
Withoutthe SPOT:
- SCSS
...
@media (max-width: 720px) {
padding: 0.25em 1em;
}
...
But we can also easily make a mixin. For example, I've encountered this on other projects:
- SASS
...
+responsive('{<md}')
padding: 0.25em 1em
...
- SCSS
...
@include less-than(md) {
padding: 0.25em 1em;
}
...
Element block separators
If you like the visual aid, how to separate the element blocks, you can do it again with the use of comments. In fact, that was the evolution of the SPOT methodology. Later we introduced mixin so that it could be checked when parsing SASS code whether the separator was really used.
- SASS
...
+_____________________________
+element('.icon')
...
- SCSS
...
//_____________________________
.icon {
...
States and variants in the sub-element
However, we started to encounter the first problems in the sub-elements. We can no longer use the &
symbol, because we want to expand the selector of the root element.
Therefore we have to reset the current selector with @at-root
and write it all over again.
With the SPOT:
- SASS
... +_____________________________ +element('.icon') +default display: inline-block width: 1.5em height: 1.5em opacity: 0.75 +state(':hover') transform: scale(1.15) +variant('.primary') opacity: 1 ...
Without the SPOT:
- SCSS
...
//_____________________________
.icon {
display: inline-block;
width: 1.5em;
height: 1.5em;
opacity: 0.75;
@at-root .btn:hover .icon {
transform: scale(1.15);
}
@at-root .btn.primary .icon {
opacity: 1;
}
...
The reason for this is that most of the time you style the whole selector in a separate block, where you mention all the sub-elements, but this breaks the SPOT rule that one element is in one contiguous block:
...
&:hover {
background: $col-btn-bg-hover;
.icon {
transform: scale(1.15);
&::before {
opacity: 1;
}
}
}
...
And selectors can be much more complex and then you don't even know what was changed, added and on which element or sub-element:
...
@at-root .btn.primary:hover .wrapper.full-width .icon.disabled::before {
...
States and variants in the pseudo-element
In the pseudo-elements it is the very same case:
With the SPOT:
- SASS
... +_____________________________ +pseudo-element('before') +default content: '\0192' display: inline-block font-family: icons opacity: 0.8 +state(':hover') opacity: 1 +variant('.primary') text-shadow: 0 0 0.5em black ...
Without the SPOT:
- SCSS
...
//_____________________________
&::before {
content: '\0192';
display: inline-block;
font-family: icons;
opacity: 0.8;
@at-root .btn:hover .icon::before {
opacity: 1;
}
@at-root .btn.primary .icon::before {
text-shadow: 0 0 0.5em black;
}
...
Contexts in the sub and pseudo-element
For contexts and responsiveness it works the same in pure SCSS, so no problem:
The reason is that we put the context before the current selector, so it works even in nesting:
- SCSS
.btn {
...
.modal & {
display: block;
}
...
//_____________________________
.icon {
...
.modal & {
opacity: 0.85;
}
...
- CSS
.btn {
...
.modal .btn {
display: block;
}
...
.btn .icon {
...
.modal .btn .icon {
opacity: 0.85;
}
...
But the SPOT CSS framework can do "magic" with contexts as well. Let's look at such special cases:
- SASS
...
+context('html.loaded')
+default
...
+context('html.scrolled')
opacity: 1
...
- CSS
...
html.loaded.scrolled .btn {
opacity: 1;
}
...
- SCSS
...
html.loaded & {
html.scrolled & {
opacity: 1;
}
}
...
- CSS
...
html.loaded html.scrolled .btn {
opacity: 1;
}
...
As you can see above, the nested contexts are not transformed into the correct selector with &
approach.
Other use case / magic:
- SASS
...
+context('html.scrolled')
+default
...
+context('body.page--index')
opacity: 1
...
- CSS
...
html.scrolled body.page--index .btn {
opacity: 1;
}
...
- SCSS
...
html.scrolled & {
body.page--index & {
opacity: 1;
}
}
...
- CSS
...
body.page--index html.scrolled .btn {
opacity: 1;
}
...
One more complicated example:
- SASS
...
+context('html.scrolled')
+default
...
+context('ui-modal')
...
+context('ui-modal.large')
...
+context('body.page--index')
opacity: 1
...
- CSS
...
html.scrolled ui-modal.large body.page--index .btn {
opacity: 1;
}
...
- SCSS
...
html.scrolled & {
ui-modal & {
ui-modal.large & {
body.page--index & {
opacity: 1;
}
}
}
}
...
- CSS
...
body.page--index ui-modal.large ui-modal html.scrolled .btn {
opacity: 1;
}
...
Responsiveness in the sub and pseudo-element
The only thing that works easily even in pure SCSS is responsiveness. Even in nested elements and pseudo-elements.
With the SPOT:
- SASS
... +responsive('{<md}') padding: 0.25em 1em +_____________________________ +element('.icon') ... +_____________________________ +pseudo-element('before') ... +responsive('{<md}') transform: scale(1.25) ... +_____________________________ +element('.text') ... +responsive('{<md}') white-space: normal
Without the SPOT:
- SCSS
...
@include less-than(md) {
padding: 0.25em 1em;
}
//_____________________________
.icon {
...
//_____________________________
&::before {
...
@include less-than(md) {
transform: scale(1.25);
}
}
}
//_____________________________
.text {
...
@include less-than(md) {
white-space: normal;
}
}
}
So what is the conclusion?
It is possible to clean up and maintain code organized according to SPOT CSS methodology even without framework and special mixins, but the code is more complex and complex. And some edge-cases are solved really badly. So...
...it's possible, but why would someone do that when there is SPOT CSS framework?
If you have a different opinion, if you disagree with something, or if I made a mistake somewhere, or if I don't see something according to you, let me know on our Discord - Johnny Seyd (@Seyd#6245).