Skip to main content

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:

+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:

.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:

...
+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:


.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:

...
+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:

...  
&: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:

...
+context('.modal')
display: block
...

Without the SPOT:

...  
.modal & {
display: block;
}
...

Responsiveness in the root element

This is very straightforward as well.

With the SPOT:

...
+responsive('{<md}')
padding: 0.25em 1em
...

Withoutthe SPOT:

...  
@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:

...
+responsive('{<md}')
padding: 0.25em 1em
...

...  
@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.

...
+_____________________________
+element('.icon')
...

...  
//_____________________________
.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:

...   +_____________________________   +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:

...
//_____________________________
.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:

...      +_____________________________      +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:

...
//_____________________________
&::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:

.btn {
...

.modal & {
display: block;
}

...
//_____________________________
.icon {
...

.modal & {
opacity: 0.85;
}

...
.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:

...
+context('html.loaded')
+default
...
+context('html.scrolled')
opacity: 1
...
...



html.loaded.scrolled .btn {
opacity: 1;
}
...
...
html.loaded & {

html.scrolled & {
opacity: 1;
}
}
...
...


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:

...
+context('html.scrolled')
+default
...
+context('body.page--index')
opacity: 1
...
...



html.scrolled body.page--index .btn {
opacity: 1;
}
...
...
html.scrolled & {

body.page--index & {
opacity: 1;
}
}
...
...


body.page--index html.scrolled .btn {
opacity: 1;
}

...

One more complicated example:

...
+context('html.scrolled')
+default
...
+context('ui-modal')
...
+context('ui-modal.large')
...
+context('body.page--index')
opacity: 1
...
...







html.scrolled ui-modal.large body.page--index .btn {
opacity: 1;
}
...
...
html.scrolled & {

ui-modal & {

ui-modal.large & {

body.page--index & {
opacity: 1;
}
}
}
}
...
...






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:

...     +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:

...
@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).