The Zen of Just Writing CSS
svelte.technology
svelte.technology
Less on-topic, I feel like the title of this article is wrong: you're not "Just Writing CSS" if you're writing scoped CSS, which requires some sort of build system.
You make a good point about consistency. Personally I'll often have a small global stylesheet that implements basic typography/colour/etc rules — it's more important to ensure that your component styles don't leak out than avoiding global styles leaking in, in my experience.
Also, what's not fully clear to me, what if - as usually is the case - the components are nested? Changing eg. font in the outer component would leave inner components fully intact?
A side note, I'm wondering how the Svelte approach relates to `isolation` in cycle.js https://github.com/cyclejs/cyclejs/issues/259 -
If components are nested, inheritance still happens (unless you're compiling to web components with Shadow DOM, which Svelte allows), but cascading doesn't unless you opt-in per-selector with the global:(...) modifier borrowed from CSS modules.
Re isolation, if I've understood correctly, then state in components is indeed isolated.
I don't get what you are trying to say. If you put your stile inside a div, and set the "scoped" attribute, it's scoped. What build system is required?
About the overall consistency, yes, some things must be global. It would be best if CSS allowed you to define those in one place and apply in another one. Yet it doesn't, so you must keep all the declarations touched by global data at a global style. That's bad, but what is even worse is putting the local declarations there too.
Instead, the article calls for using Svelte and its build tools to automatically add unique attributes to HTML elements and add corresponding selectors to CSS.
Svelte is awesome, but there's no doubting that it is a build tool, and I think it's fair to argue that if you're committing to a build tool either way, maybe you'd be just as happy using a richer build tool based on TypeScript and styled React components or whatever floats your boat.
[1] https://developer.mozilla.org/en-US/docs/Web/HTML/Element/st...
The client-side polyfill for Shadow DOM requires executing JS before paint, which is slow. For optimum performance, you have to polyfill it server side, with a build tool. At that point, you might as well just pre-process it for all browsers, at which point, maybe you'd enjoy something more flexible than Shadow DOM, e.g. something that lets you share styles between components.
The ShadyDOM/ShadyCSS Shadow DOM shims are quite fast, and the benefit is that you don't need them when you have native Shadow DOM, vs a compile-time solution that you're likely married to and is proprietary. Shadow DOM gets faster automatically as browsers adopt standards, and portable and lasting knowledge is worth a lot.
Safari's Shadow DOM implementation has improved a ton over the last year. Polymer 2.0 defaults to using it over the polyfills. caniuse.com needs to be updated - the second bug listed is fixed now.
But nobody can beat streaming server-rendered (or ServiceWorker-rendered) HTML + CSS on a "time to paint" basis. Google claims that "time to paint" is the wrong measure, but if you're using progressive enhancement, TTP is all you need.
Shadow DOM mostly sucks anyway for lack of a good way of sharing styles between shadow roots. (It's awesome that we can encapsulate CSS with shadow DOM, but that was too much encapsulation.)
I think we can distinguish two orthogonal uses for CCS styles. One is applying a global style to the page/app ("theming", "branding", etc) and the other is for defining the layout of a component. For technical reasons they have lived together, but with shadow DOM One can put the former on a global CSS and the latter togheter with the component. By doing this I think you can remain consistent, in fact I suspect that it may improve consistency since it is better clear what is part of the style (and thus should be respected/changed with care) and what is an implementation detail of a single piece of a page.
Of course the risk of a component "going rogue with styles" exists, but maybe (and now I am just speculating) by adding CSS variables to the mix we can help the single components to be more consistent with the rest of the app.
Typography and colors are something almost everyone tries to keep within a bounded set, but spacings are often overlooked. If the designer has defined a clear set of spacings - like 8px for small, 24 for medium, and 48px for large, then we can build consistent interfaces by using that as a primitive. Button paddings will be $small-spacing, while sections can be separated by $large-spacing etc.
The stylesheet at this point is just an application of all these base values to the HTML structure of the particular component, and that arrangement can be unique to each component depending on how its HTML is laid out.
http://tachyons.io does this really well - @mrmrs_ has teased out a universal set of base values that can cover most designs, and has built out a complete UI kit out of it. There are many experience reports online, here's how DNSimple went about it - https://blog.dnsimple.com/2017/01/CSS-journey/.
All global styles achieve is to make it difficult to break out of conventions when our layouts and designs diverge on purpose.
Visual consistency begs for DRY CSS, and DRY CSS tends to bend back towards single global stylesheets.
It's fine to still have a (preferably fairly small!) global stylesheet that covers fonts, colours and so on. The key is to avoid your components potentially clobbering each other, which is what SFCs accomplish.
1) http-call bloat is mitigated by a built process (which you would probably already have to use if you're using a Scoped CSS method like CSS Modules.
2) Visual consistency and DRY aren't affected. Think about it like regular code - when we want to repeat a unit of code we break it out into a seperate compassable function. We can do the same thing in CSS Modules with composes. You could also (carefully) introduce a preprocessor like LESS/SASS/Stylus/etc for variables and mixins and what not.
With tachyons I still wrap up styles in components but I also get globally scoped CSS for consistency with fonts/spacing.
I find that keeping what I do simple and small and well documented makes the simple stuff I do easy to maintain.
Yes, I can write you (another) 100kloc of Java or C++ or JS if you really want, but I did also enjoy Standard ML and Prolog over more and more imperative code with exciting race and portability issues.
To do CSS at scale requires organization and planning from the very beginning. If you start with bootstrap, you can't just deviate down the line. If you follow the BEM convention you not only have to stick to it, you have to enforce it so everyone in the team follows it.
Currently I work on 2 MVC project, and just like every component has a view, each also has it's own css file. We don't have to wait for compilation since .net has a realtime css update, and php doesn't need to compile in the first place. But for each project, i have to train new members to follow the convention.
Agree with this. I've seen frontend work collapsing under its own weight because the CSS was kept in one huge file, you couldn't tell what was dependent on what, JavaScript was being used to alter the DOM based on classes and window resizes, there were around 20 different media query size widths, nothing was shared as variables...changing anything was terrifying and took forever.
You need to keep CSS modular, stick to naming conventions, reduce duplication and keep code concise; exactly like you do with regular programming languages. You can get far with CSS without any programming knowledge but at a certain scale you need to understand good coding fundamentals.
> 2. A child component's root node will be affected by both the parent's scoped CSS and the child's scoped CSS.
> 5. Be careful with descendant selectors in recursive components! For a CSS rule with the selector .a .b, if the element that matches .a contains a recursive child component, then all .b in that child component will be matched by the rule.
https://github.com/vuejs/vue-loader/blob/master/docs/en/feat...
If it's the same in Svelte I don't see how scoped CSS solves anything. I still need to be careful not to let the CSS bleed into the child components.
You can also compile Svelte components to custom elements, which use shadow DOM to enforce complete style isolation (though this is a new and experimental option).
In practice, this means not to use selectors like .a * which is pretty easy to avoid (and you probably should be anyway)
I work with Vue and Stylus. You already have to give your JS components a name. Your styles can use that name. I don't need my styles inside the .vue files. I just keep them in a separate file: /styles/components/foo.styl. At the top of your Stylus file you give the name of the component (.foo) once. Even further, you can prefix it (.my-foo). That's the namespace. Then all your styles can be written under that with a methodology like BEM. Now you get scoping AND you can still load up your variables/settings first, via a main.styl. Otherwise, if you put styles in your .vue files then you need to remember to import variables each time. Also it becomes an issue to discern which classes in the component are from you and which are from a vendor (ie. Bulma, Boostrap). Then think about libraries. You can't work with styles that are in the vendor's .vue files (without adding more specificity). I wish that library would have provided me its separate SCSS/Stylus styles instead. Am I missing something here Rich? (btw, love your work in Rollup!)
But support for external files and preprocessors is on our roadmap.
-- All separate style files (for non-vue-component styling, such as variables, functions, and CSS-only components) use BEM. All styles inside Vue components also use BEM. Consistency!
-- Use Stylus in the Vue components. Vue-loader supports preprocessors. Why a preprocessor? Mainly for theming. Why Stylus? It’s just so beautiful.
-- Using BEM inside vue components means I don’t have to worry about collisions (with global, or children, or vendors).
-- Mental context-switching is reduced now since component styling lives inside the .vue files.
-- I don’t mind using #app to override vendor component styling.
-- Better scenario for Dead Code Elimination to work with when putting styles in .vue files.
-- I don’t mind importing variables/functions/theming to each component until this is solved in the future. Plus, I could create a bootstrap.styl (not to be confused with Twitter Bootstrap) to import everything I need so I’m only adding a single file to each Vue component. Any changes made are made within boostrap.styl and Vue components would not need to be updated.
Hmm, I don't find namespace issues a problem to be honest. Generally I use a framework (e.g. Bootstrap) where I don't reuse any of its names, I have some global styling (e.g. buttons, forms, typography), each major screen/component gets it's own file with its own class identifier (e.g. page-home for the homepage) then in SASS/SCSS I make sure everything everything in each file is nested under the screen/component identifier (e.g. so everything to do with the homepage is nested under .page-home). It's going to be very obvious if you get a naming conflict and they're not hard to resolve.
Is there anything wrong with the above approach? You'd need something different if you were designing your own framework or general components across a huge website but for SPA and typical websites I don't see the need to make it more complex than this.
I think you've actually agreed that Scoped CSS is really useful, but you just implement it manually by using a naming convention. What if you didn't have to worry about that and the language just worked like that normally?
I agree scoping is universally useful in coding, I just can't empathise that the lack of proper namespacing in CSS is a huge headache when it takes little effort to emulate it by nesting classes. Unless there's more to it? Language support is nice though.
Many times in larger web applications, designers will have similar design themes that span multiple pages (more than just simple things like forms). So there may be a callout with a border and all text inside is blue across 5 separate pages.
You may approach this as #homepage .callout at first but it begins to become hard to manage when you realize you need to refactor it into a side wide style.
Generally I approach everything in attempts to make it re-usable. And organize what I can into components. I'd rather write more css classes in the markup than have to specify names for certain cases where 'this heading is bold here but underlined in this case' yada yada, when I can just write .bold .underline .h5.
So one approach would be to have a "callout" css file, nest everything under ".callout" and have several callout styles you can use inside that file (e.g. ".callout .warning")?
> Generally I approach everything in attempts to make it re-usable. And organize what I can into components
Personally, I find very little CSS can be reused between different projects as designers want completely different looks so I let things like Bootstrap take care of the basics and customise from there. It takes time and effort to make things reusable (same with code) so you need to weigh up how likely you are to actually reuse things and how much time that would save you.
Sometimes duplication between projects saves time compared to maintaining the code for some generic widget that has to work in many scenarios. For me, I don't think it would save much time as each component is usually small and simple but I could understand if you were to create e.g. a calendar widget where it involves a lot of complex styling and code.
* have a small global stylesheet with styles that are common to many components * put the styles in a mutual parent component and opt-in to the cascade with the :global(...) modifier (from CSS modules) * you don't. This is often the answer I prefer. DRY code is less flexible, and I've often found it far better to have some duplication 5 minutes before I ship rather than be desperately trying to change some CSS on this component without also affecting those components
But yes, this is an area we still need to work on.
The answer for this is already in your browser: CSS custom properties http://caniuse.com/#feat=css-variables
On top of that the CSS working group is working on ::part and ::theme pseudoselectors to help theming across components. Brief intro in this Polymer Summit 2017 talk: https://youtu.be/rvpJ5O0W_6A?list=PLNYkxOF6rcIDP0PqVaJxqNWwI... (skip back to 16:17 to hear why the CSS mixins proposal didn't work out)
So... is `el.classList.add('error')` going to do nothing if the source contains an otherwise unused error class? Is there some comment flag that prevents this behavior? Do you need to shut off the analyzer completely, or add extra hidden markup to also consume your dynamic classes?
Does it handle media queries? Or new/upcoming CSS features like the replacement for the attempted mixin standard? (:theme and... something or other...)
I'm all for finding dead rules, but if it automatically eliminates them I worry it'll be easy to accidentally trick.
Yep, it handles media queries. It should handle ::theme just fine (since it doesn't know what it is, it'll just ignore it), but since it's not implemented anywhere I haven't tested that yet!
Sounds good, thanks for clarifying.
* As the author of that article was talking about, I keep as much CSS scoped to the component level as possible.
* Any layout-oriented styling(how a component is positioned and sized on a given page) is separated into its own style sheet per page. That way how a page is laid out is separated from how a particular component looks & behaves.
* To deal with the lack of container/element queries, a big roadblock in making components truly reusable, a component style sheet exposes Sass mixins that represent how the component looks at different sizes. The layout CSS for a given page, as mentioned in the last bullet point, includes these mixins at different media query breakpoints. The advantage to this is that the layout can manipulate a component at different breakpoints to create responsiveness without needing to have any knowledge of a component's internal workings.
An example of my third point is a component I'm currently working on right now called `incidents-grid`, which is merely a grid of items. The `components/incidents-grid.sass` stylesheet has mixins called `incidents-grid--xl`, `incidents-grid--lg`, `incidents-grid--med`, `incidents-grid--sm`, `incidents-grid--xs`. Other components follow the same convention. For my page, I have a `layout/index.sass` to deal with how I lay out my components on the page. At different media query breakpoints, I will include the incidents-grid mixins to change how the component looks at different sizes; at different sizes, this component may show or not show a thumbnail, or show smaller icons, or no icons, or omit some information. The layout can make these things appear/disapper at different screen sizes without a bunch of CSS specific to the component saying things like `.incidents-grid__thumbnail {display: none}`. Rather, my layout stylesheet can say something like `#l-index__incidents-grid { @include incidents-grid--sm }` in a media query to accomplish the same thing.
Granted, this can't be done with plain CSS without some JavaScript. But I've found being able to separate layout CSS from pure styling CSS, as well as abstracting the internal workings of components with mixins, to be a very sane approach.
Ultimately it's something you can DIY in ~30 lines of JavaScript, and over the weekend (just for fun, just to see how far back the ideas worked) I got it running in IE8, IE7, and even IE6 - so it's definitely something you can use today! https://twitter.com/innovati/status/903998563738415104
The key idea is that with CSS and DOM scoping you write simpler, easier to understand, stylesheets and templates, which massively improves maintainability.
In Polymer elements, because of Shadow DOM, selectors are considerably simpler and shorter, often just using a tag name or id. Templates for a single component often fit in one screen, so you can more easily see if a selector is used.
As we get into more declarative template formats and better static analysis, we'll be able to see if selectors are used even for dynamic classes and attributes.
If you string-replace "CSS" with "assembly", most of Bret Victor's "The Future of Programming" [1] still seems to apply. People used to argue this passionately that knowing how to write assembly by hand was critical to being a good programmer.
> "Love it or loathe it, you must at least learn it."
This might be true today, but it would be a shame if tooling didn't level up to a degree where CSS becomes mostly a compile target (just like assembly is today).
At the end of the day, CSS provides styling tools for a massive medium, one that should be accessible much further beyond the 0.25% of the world that knows how to code. The main way we can unlock that is with better tools and better abstractions. It's wholly unrealistic to get everyone to be as good at hand-crafting CSS as the author, nor should it be the goal.
CSS is getting more and more powerful and more complex, and it's unrealistic for folks to continue hand-crafting in perpetuity. Even today, with some of the world's best software engineers, I routinely see this:
* Most devs don't know how to create a radial gradient from memory
* Most devs don't know how to hand code shape-outside code, especially polygon
* Most devs guess-and-check repeatedly as they work with Flexbox props and use cheatsheets even after years of practice (while designers are running circles around them using visual abstractions like [2])
* CSS Grid minmax formats, don't get me started...
I personally love crafting CSS by hand, there's a certain satisfaction to it. But if that's the primary way that folks are styling stuff on the web 5 years from now, to me that would imply that we did a bad job of exposing this wonderful technology to orders of magnitude more people.
---
I think I largely agree with you — giving more people the ability to build and design stuff with web technologies is something I care a lot about. To me, the answer is to build better tools around CSS, rather than creating a multitude of abstractions and creating tools that target those.
My belief is that CSS tooling hasn't evolved as quickly as JS tooling (and hence CSS-in-JS) because the JS community has often had a bit of a snooty attitude towards CSS and people who write it. That's something I hope we can change.
I'm just a bit lairy of adding yet another proprietary tool to my buildchain to try and bend the web platform to my will.
> I'm just a bit lairy of adding yet another proprietary tool to my buildchain to try and bend the web platform to my will.
By using Svelte I've found that I no longer need to bother with any CSS pre/postprocessors or minifiers (I was mainly using them for scoping, which is no longer necessary), and I don't need to faff around transpiling JSX. So you might find you add one build tool but get rid of two others!
LESS and SASS have more to do with the fine-grained details of CSS syntax... primarily reducing repetition (via nesting, mixins, variables, etc).
I personally wouldn't worry about any one particular technology (other than CSS itself), but I have found over the years that the most immensely helpful technique when working with CSS is keeping things as modular as possible. Basically, ignore the "C" (Cascade) in CSS, either with compiled / scoped components (like the linked article is talking about) or through a naming convention like BEM or SMACSS.
So how do you get there from 0?
1) Read this page: https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduct... and then play this game. https://flukeout.github.io/
2) Open a file in a text editor with a <style> element and a <body> element to play around with.
3) Enter file:///path/to/that/file so you can view it.
4) Download a Spaced Repetition flashcard app like https://apps.ankiweb.net/ For more on how Spaced Repetition works, see https://www.youtube.com/watch?v=eVajQPuRmk8.
5) With all that open, work through http://book.mixu.net/css/ As you are going through it, test things out yourself and take notes. Make flashcards that test your ability to remember why certain things work the way that they do. Also make flashcards for things that seem surprising. Try to keep them small and focused.
6) When you get to the flexbox section, take a break and play this game: http://flexboxfroggy.com/
If you are the sort of person who likes understanding things at a deeper layer of abstraction than you actually work, read https://www.html5rocks.com/en/tutorials/internals/howbrowser...
I get the advantage of using either Sass or less or postcss with webpack and I get all the features as expected from this workflow.
The only thing I don't have is that unused class feature with my workflow, but I can probably find a postcss plugin which handles that.
I don't see any advantage of this library for me?
You're unlikely to find a postcss plugin that can eliminate dead code. Unless it can analyse your CSS in the context of your markup, it has no way of knowing what's safe to remove. For that, you need statically analyzable single file components.
// styles.css
.root { background: grey; }
.button { border: 1px solid blue; }
.error { color: red }
.alert { background: red }
// Component.css
import styles from './styles.css'
return (
<div className={styles.root}>
<button className={styles.button}>Submit</button>
{false && <div className={styles.error}>Error!</div>}
</div>
)
Both the .error and .alert are candidates for being removed.Of course, I'm not aware of any solution for nothing this currently, but all the bits and pieces are there for it to be feasible.
It's easy to just say "don't do that then" but with a large enough codebase, and devs of many different skill levels, it's easier just to move to a model that prevents CSS cross-talk altogether.
In our codebase (using import './styles.css' w/CSS Modules) every single component has a className called .root at the very top. Never conflicted once.
Nowadays a website is no longer just a document. It's an application. Why have html/css the one crude way to make a web application? In the backend we have a constellation of apis, frameworks and languages to build an app, but in the front end it's HTML/CSS/JS. The role JS played is generalized by WebAssembly, it's time to do the same for HTML/CSS.
WA gives us performance. Before WA you could already write with many languages and output JS.
we even had IDEs back in the day that compiled wysiwyg into that code.
we're repeating everything but just in a prettier way.