Tag: “Stacks”

Tech Stacks and Website Longevity

Steren Giannini in “My stack will outlive yours”:

My stack requires no maintenance, has perfect Lighthouse scores, will never have any security vulnerability, is based on open standards, is portable, has an instant dev loop, has no build step and… will outlive any other stack.

Jeremy Keith in “npm ruin dev”:

Instead of reaching for all-singing all-dancing toolchain by default, I’m going to start with a boring baseline. If and when that becomes too painful or unwieldy, then I’ll throw in a task manager. But every time I add a dependency, I’ll be limiting the lifespan of the project.

I like both of those sentiments.

Steren’s “stack” is HTML and CSS only. Will HTML and CSS “last” in the sense of that website being online and working for a long time. I’d say certainly yes. HTML and CSS were around before I got here, are actively developed, and no other technologies are really even trying to unseat them. The closest threats are native platforms, but those are so fractured, closed, while lacking the worldwide utility of the URL and open standards, that it doesn’t look like that any native platform will unseat the web. It’s more likely (and we see this happening, even if it’s slow and fraught) that native platforms embrace the web.

Will an HTML and CSS website be perfectly functional in, say, 2041? I’d say certainly. I’ll bet ya a dollar.

Steren doesn’t mean that HTML and CSS is just the output, but there is still other tooling to get there. No build process either. No templating. Need to update the navigation?

So… if I don’t use any templating system, how do I update my header, footer or nav? Well, simply by using the “Replace in files” feature of any good text editor. They don’t need frequent updates anyway. The benefits of using a templating system is not worth the cost of introducing the tooling it requires.

I admit this is drawing the line further back than I would. This feels just like trading one kind of technical debt for another. Now you’ll need to write scripts or an elaborate find-and-replace RegEx to do what you want to do, rather than reach for some form of HTML include, which there are a ton of ways to handle lightly.

But I get it. Especially since once you do add that one templating language (or whatever), the temptation is strong to keep adding to the system, introducing more and more liabilities with less consideration on how they may be “limiting the lifespan” of the project.

I don’t actually think the stack matters that much.

In thinking about sites I work on (and have worked on), the longevity of the site doesn’t feel particularly related to the stack. Like, at all. The sites with the longest lifespans (like this one) have long lifespans because I care about them, and they have all sorts of moving parts in the stack.

I pick technology to help with what I want to do. If my needs change, I change the technology. I don’t just say, ooops, my stack is off, I guess I’ll shut down the website forever.

If we’re talking about website longevity, I think the breakdown of how much things matter is more like this:

  • 80% How much I care about the website
  • 10% The website isn’t a financial burden
  • 5% The website isn’t a mental burden (“the stack” being some small part of this)
  • 5% I have access to the registrant and didn’t forget to renew the domain name before a squatter nabs it

The post Tech Stacks and Website Longevity appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,

Using Custom Property “Stacks” to Tame the Cascade

Since the inception of CSS in 1994, the cascade and inheritance have defined how we design on the web. Both are powerful features but, as authors, we’ve had very little control over how they interact. Selector specificity and source order provide some minimal “layering” control, without a lot of nuance — and inheritance requires an unbroken lineage. Now, CSS Custom Properties allow us to manage and control both cascade and inheritance in new ways.

I want to show you how I’ve used Custom Property “stacks” to solve some of the common issues people face in the cascade: from scoped component styles, to more explicit layering of intents.

A quick intro to Custom Properties

The same way browsers have defined new properties using a vendor prefix like -webkit- or -moz-, we can define our own Custom Properties with an “empty” -- prefix. Like variables in Sass or JavaScript, we can use them to name, store, and retrieve values — but like other properties in CSS, they cascade and inherit with the DOM.

/* Define a custom property */ html {   --brand-color: rebeccapurple; }

In order to access those captured values, we use the var() function. It has two parts: first the name of our custom property, and then a fallback in case that property is undefined:

button {   /* use the --brand-color if available, or fall back to deeppink */   background: var(--brand-color, deeppink); }

This is not a support fallback for old browsers. If a browser doesn’t understand custom properties, it will ignore the entire var() declaration. Instead, this is a built-in way of handling undefined variables, similar to a font stack defining fallback font families when one is unavailable. If we don’t provide a fallback, the default is unset.

Building variable “stacks”

This ability to define a fallback is similar to “font stacks” used on the font-family property. If the first family is unavailable, the second will be used, and so on. The var() function only accepts a single fallback, but we can nest var() functions to create custom-property fallback “stacks” of any size:

button {   /* try Consolas, then Menlo, then Monaco, and finally monospace */   font-family: Consolas, Menlo, Monaco, monospace;    /* try --state, then --button-color, then --brand-color, and finally deeppink */   background: var(--state, var(--button-color, var(--brand-color, deeppink))); }

If that nested syntax for stacked properties looks bulky, you can use a pre-processor like Sass to make it more compact.

That single-fallback limitation is required to support fallbacks with a comma inside them — like font stacks or layered background images:

html {   /* The fallback value is "Helvetica, Arial, sans-serif" */   font-family: var(--my-font, Helvetica, Arial, sans-serif); }

Defining “scope”

CSS selectors allow us to drill down into the HTML DOM tree, and style elements anywhere on the page, or elements in a particular nested context.

/* all links */ a { color: slateblue; }  /* only links inside a section */ section a { color: rebeccapurple; }  /* only links inside an article */ article a { color: deeppink; }

That’s useful, but it doesn’t capture the reality of “modular” object-oriented or component-driven styles. We might have multiple articles and asides, nested in various configurations. We need a way to clarify which context, or scope, should take precedence when they overlap.

Proximity scopes

Let’s say we have a .light theme and a .dark theme. We can use those classes on the root <html> element to define a page-wide default, but we can also apply them to specific components, nested in various ways:

Each time we apply one of our color-mode classes, the background and color properties are reset, then inherited by nested headings and paragraphs. In our main context, colors inherit from the .light class, while the nested heading and paragraph inherit from the .dark class. Inheritance is based on direct lineage, so the nearest ancestor with a defined value will take precedence. We call that proximity.

Proximity matters for inheritance, but it has no impact on selectors, which rely on specificity. That becomes a problem if we want to style something inside the dark or light containers.

Here I’ve attempted to define both light and dark button variants. Light mode buttons should be rebeccapurple with white text so they stand out, and dark mode buttons should be plum with black text. We’re selecting the buttons directly based on a light and dark context, but it doesn’t work:

Some of the buttons are in both contexts, with both .light and .dark ancestors. What we want in that case is for the closest theme to take over (inheritance proximity behavior), but what we get instead is the second selector overriding the first (cascade behavior). Since the two selectors have the same specificity, source order determines the winner.

Custom Properties and proximity

What we need here is a way to inherit these properties from the theme, but only apply them to specific children. Custom Properties make that possible! We can define values on the light and dark containers, while only using their inherited values on nested elements, like our buttons.

We’ll start by setting up the buttons to use custom properties, with a fallback “default” value, in case those properties are undefined:

button {   background: var(--btn-color, rebeccapurple);   color: var(--btn-contrast, white); }

Now we can set those values based on context, and they will scope to the appropriate ancestor based on proximity and inheritance:

.dark {   --btn-color: plum;   --btn-contrast: black; }  .light {   --btn-color: rebeccapurple;   --btn-contrast: white; }

As an added bonus, we’re using less code overall, and one unified button definition:

I think of this as creating an API of available parameters for the button component. Sara Soueidan and Lea Verou have both covered this well in recent articles.

Component ownership

Sometimes proximity isn’t enough to define scope. When JavaScript frameworks generate “scoped styles” they are establishing specific object-element ownership. A “tab layout” component owns the tabs themselves, but not the content behind each tab. This is also what the BEM convention attempts to capture in complex .block__element class names.

Nicole Sullivan coined the term “donut scope” to talk about this problem back in 2011. While I’m sure she has more recent thoughts on the issue, the fundamental problem hasn’t changed. Selectors and specificity are great for describing how we build detailed styles over top of broad patterns, but they don’t convey a clear sense of ownership.

We can use custom property stacks to help solve this problem. We’ll start by creating “global” properties on the <html> element that are for our default colors:

html {   --background--global: white;   --color--global: black;   --btn-color--global: rebeccapurple;   --btn-contrast--global: white; }

That default global theme is now available anywhere we want to refer to it. We’ll do that with a data-theme attribute that applies our foreground and background colors. We want the global values to provide a default fallback, but we also want the option to override with a specific theme. That’s where “stacks” come in:

[data-theme] {   /* If there's no component value, use the global value */   background: var(--background--component, var(--background--global));   color: var(--color--component, var(--color--global)); }

Now we can define an inverted component by setting the *--component properties as a reverse of the global properties:

[data-theme='invert'] {   --background--component: var(--color--global);   --color--component: var(--background--global); }

But we don’t want those settings to inherit beyond the donut of ownership, so we reset those values to initial (undefined) on every theme. We’ll want to do this at a lower specificity, or earlier in the source order, so it provides a default that each theme can override:

[data-theme] {   --background--component: initial;   --color--component: initial; }

The initial keyword has a special meaning when used on custom properties, reverting them to a Guaranteed-Invalid state. That means rather than being passed along to set background: initial or color: initial, the custom property becomes undefined, and we fallback to the next value in our stack, the global settings.

We can do the same thing with our buttons, and then make sure to apply data-theme to each component. If no specific theme is given, each component will default to the global theme:

Defining “origins”

The CSS cascade is a series of filtering layers used to determine what value should take precedence when multiple values are defined on the same property. We most often interact with the specificity layers, or the final layering based on source-order — but the first layer of cascade is the “origin” of a style. The origin describes where a style came from — often the browser (defaults), the user (preferences), or the author (that’s us).

By default, author styles override user preferences, which override browser defaults. That changes when anyone applies `!important` to a style, and the origins reverse: browser `!important` styles have the highest origin, then important user preferences, then our author important styles, above all the normal layers. There are a few additional origins, but we won’t go into them here.

When we create custom property “stacks,” we’re building a very similar behavior. If we wanted to represent existing origins as a stack of custom properties, it would look something like this:

.origins-as-custom-properties {   color: var(--browser-important, var(--user-important, var(--author-important, var(--author, var(--user, var(--browser)))))); }

Those layers already exist, so there’s no reason to recreate them. But we’re doing something very similar when we layer our “global” and “component” styles above — creating a “component” origin layer that overrides our “global” layer. That same approach can be used to solve various layering issues in CSS, which can’t always be described by specificity:

  • Override » Component » Theme » Default
  • Theme » Design system or framework
  • State » Type » Default

Let’s look at some buttons again. We’ll need a default button style, a disabled state, and various button “types,” like danger, primary and secondary. We wan’t the disabled state to always override the type variations, but selectors don’t capture that distinction:

But we can define a stack that provides both “type” and “state” layers in the order that we want them prioritized:

button {   background: var(--btn-state, var(--btn-type, var(--btn-default))); }

Now when we set both variables, the state will always take precedence:

I’ve used this technique to create a Cascading Colors framework that allows custom theming based on layering:

  • Pre-defined theme attributes in the HTML
  • User color preferences
  • Light and dark modes
  • Global theme defaults

Mix and match

These approaches can be taken to an extreme, but most day-to-day use-cases can be handled with two or three values in a stack, often using a combination of the techniques above:

  • A variable stack to define the layers
  • Inheritance to set them based on proximity and scope
  • Careful application of the `initial` value to remove nested elements from a scope

We’ve been using these custom property “stacks” on our projects at OddBird. We’re still discovering as we go, but they’ve already been helpful in solving problems that were difficult using only selectors and specificity. With custom properties, we don’t have to fight the cascade or inheritance. We can capture and leverage them, as-intended, with more control over how they should apply in each instance. To me, that’s a big win for CSS — especially when developing style frameworks, tools, and systems.

The post Using Custom Property “Stacks” to Tame the Cascade appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]