name: CSS Architecture class: middle, center, title
???
In this presentation, I'd like to articulate a strategy for proper care and feeding of CSS.
First off, if you haven't read Jonathan Snook's take on the subject, I recommend you still check out the source material.
I have it linked up at the end.
SMACSS is a way to examine your design process and as a way to fit those rigid frameworks into a flexible thought process
???
I'll start by saying: I like the cut of Snook's jib.
CSS is a complicated profession and Jonathan leans into thought process over tools/libs/implementation details.
I like how this system focuses on separating CSS into categories and applies patterns in a more targeted fashion. CSS isn't really one thing and Snook does a good job explaining this.
- What do I call this?
- Where should I put this?
- Should I even be writing this?
- Where should I look for prior art before I likely introduce duplicate CSS?
???
Naming things is hard... and so is sorting things.
The ability to separate CSS into easy-to-communicate channels of concern will help drive our CSS architecture decisions.
- Base
- Layout
- Module
- State
- Theme
background-image: url(./assets/base.jpg) background-size: 700px auto
???
The base rules are your defaults.
This should probably come directly from the Design System, and if it doesn't, should be applied globally in the root stylesheet instead of in every component that wants sensible defaults.
Wording that a different way may make it more self evident: "Does it make sense to define a default inside view encapsulation?"
- Establishing defaults for your app
- Heavily rely on the Design system here
- Never use a !important here
// styles.scss
// styles/_base.scss
body, form {
margin: 0;
padding: 0;
}
a {
color: #039;
}
a:hover {
color: #03F;
}
???
This is the demo that Snook shares in his book.
// styles.scss
// styles/_base.scss
html,
body {
padding: 0;
margin: 0;
height: 100%;
background-color: $whitish;
color: $grey;
}
body * {
box-sizing: border-box;
font-family: $font-family-sans;
}
???
This is probably more how yours should look though
- Scoped like page layout
- Structure to compose components
- Traditionally ID selectors
- we probably want component selectors.
- Could live in a more globally accessible place
styles/_layout.scss
is common enough
- Probably should live in a set of components
shared/layout.component
feels sensible to me
???
Snook calls out that you can use the word layout at many different scopes and be correct, but what he means is essentially a "page layout" or a higher level of laying out how various components (modules) come together.
// styles/_layout.scss
#header, #article, #footer {
width: 960px;
margin: auto;
}
#article {
border: solid #CCC;
border-width: 1px 0 0;
}
???
You can see what he's going for here, the app/ page has one header and footer and this is the level at which we think about establishing how they render.
The fact that snook has IDs for header and article feels a touch dated, but the general idea holds and those are pretty good names to drive the point.
// shared/layout/layout.component.ts
@Component({
selector: 'app-layout',
template: `
<div class="aside" *ngIf="showAside">
<ng-content select="[aside]"></ng-content>
</div>
<div class="primary">
<ng-content></ng-content>
</div>
<div class="secondary" *ngIf="showDetails">
<ng-content select="[secondary]"></ng-content>
</div>
`,
styles: [`
.primary {
flex: 1;
}
.aside,
.secondary {
flex: 0 0 33.3%;
}
`]
})
???
The bulk of style of this category should probably live within a layout component, in our shared styles at the root of the project or a combination of the 2 like the full height panel layout.
I don't want to dig too deep into component composition, but it's worth mentioning that a sensible component strategy can greatly simplify your CSS strategy. Really challenge how you interpret "separation of concerns".
Are you separating in a way that makes your life easier or just making files smaller?
- Read: components
- View encapsulation simplifies this
- Still worth thinking about
???
This class of style probably makes sense to live in the context of a given component. Do be critical when considering a component should be customized.
Why do you feel you need this CSS?
Are you using the right classes and DOM structure?
Are you striving for pixel perfection implementation of a mock that is off brand?
Did you blindly copy from zeplin?
The answer to these questions should guide your hand here.
.module > h2 {
padding: 5px;
}
.module span {
padding: 5px;
}
???
See how in the book, Snook is using the name of a module to namespace the styling? If you're writing styles in a component context, view encapsulation will take care of that for you without a need to come up with clever names.
selector: 'app-card',
template: `
<h2>{{ title }}</h2>
<ng-content></ng-content>
`,
styles: [`
@import 'variables';
:host {
border: solid $border-thickness $red;
display: block;
border-radius: $radius;
padding: $gap;
background-color: $brown-light-1;
}
h2 {
background: $whitish;
border: solid $border-thickness $grey;
padding: $gap;
border-radius: 0 $radius $radius 0;
box-shadow: $outer-shadow;
}
`]
???
:host is your friend. You've already declared a selector, You don't need to wrap a div just to decorate it with something like .card .content or the like. If you want to have something akin to a module without the component implication, you could bundle such a thing up as global styles.
Considering how such styling tends to be coupled with the "right" markup to work... you probably mostly want to leverage a small presenter component to assure a convenient amount of coupling.
- State styles can apply to layout and/or module styles; and
- State styles indicate a JavaScript dependency.
.is-hidden
.is-collapsed
.is-error
???
This is the category of CSS that feels most compatible with utility classes.
Reusable classes like .is-hidden
should probably be seen as bit of a smell
considering we're not writing jquery though. Why put something in the DOM and
force the browser to parse it just to render it invisible?
If you can keep this abstract, you should and creating a utility class or
placeholder at the root of the project makes sense. When you're working with a
state modifier that only makes sense applied to a given module, define it scoped
as such. Do so either through naming convention is-tab-active
or through a
component's view encapsulation is-active
.
// in module-name.css
.mod {
border: 1px solid;
}
// in theme.css
.mod {
border-color: blue;
}
???
You probably don't need this, but I want to go over it quickly for completeness.
Note: there's not an override, this example defines the theme specific attribute in a theme file and not in the module's file.
The DS has a light and dark theme, which is one way to interpret this category. Should we lean into responsive design, you can make an argument for that sort of shift being a "mobile theme". For now, we'll mostly assume this category as not our problem.
You may use this to define different color schemes or maybe more densely packed typography for.
- But we use scss
- View encapsulation lower the stakes of name-spacing
- You shouldn't be writing a lot of base or theme styles as a rule
???
SMACSS was written as a good strategy for plane ol vanilla CSS with no consideration or recommendation for getting tied to frameworks. As such, I think the problem solved with naming may better be solved in our context by location of css. SMACSS based thinking applied to our Angular + DS architecture probably shakes out like this.
Base code should come directly from the DS and be applied globally
(src/styles/
). Keep all this out of component files.
Layout components (modules/layout-*
)
global styles (src/styles/
) will share the responsibility of layout styles.
Module is synonymous with how we see components, but we need to lean into granular components for this to work smoothly. Also, be more aware of how your base styles want to work and be more conservative about overriding that all willie nillie.
State CSS may live in the globally accessible styles (src/styles
) if it
makes sense outside of the context of your component, but is probably largely
fine living in your component so long as you've confirmed you're not writing
redundant code (this really is the enemy here).
Let's hold off on theme for the time being.
- Not about optimizing for mobile
- Is about starting with clarity
???
// styles/_nav.scss
%nav-vertical {
@extend %nav;
}
%nav-horizontal {
@extend %nav;
@media (min-width: $small) {
li {
display: inline-block;
}
a {
margin: 0 0.25em;
}
}
}
???
In this example, I define navigation to be vertically oriented by default, then alter that to fit horizontal. However, I enable the horizontal variant in a media query because it's only applicable for bigger screens.
- SMACSS
- Notes on layouts identified in Match
- Custom utility class defined in Match (can be improved, but in the right direction)
- Example of a layout component (can be improved, but in the right direction)