Tag: System

Open Props (and Custom Properties as a System)

Perhaps the most basic and obvious use of CSS custom properties is design tokens. Colors, fonts, spacings, timings, and other atomic bits of design that you can pull from as you design a site. If you pretty much only pull values from design tokens, you’ll be headed toward clean design and that consistent professional look that is typically the goal in web design. In fact, I’ve written that I think it’s exactly this that contributes to the popularity of utility class frameworks:

I’d argue some of that popularity is driven by the fact that if you choose from these pre-configured classes, that the design ends up fairly nice. You can’t go off the rails. You’re choosing from a limited selection of values that have been designed to look good.

I’m saying this (with a stylesheet that defines these classes as one-styling-job tokens):

<h1 class="color-primary size-large">Header<h1>

…is a similar value proposition as this:

html {   --color-primary: green;   --size-large: 3rem;   /* ... and a whole set of tokens */ }  h1 {   color: var(--color-primary);   font-size: var(--size-large); }

There are zero-build versions of both. For example, Tachyons is an it-is-what-it is stylesheet with a slew of utility classes you just use, while Windi is a whole fancy thing with a just-in-time compiler and such. Pollen is an it-is-what-it is library of custom properties you just use, while the brand new Open Props has a just-in-time compiler to only deliver the custom properties that are used.

Right, so, Open Props!

The entire thing is literally just a whole pile of CSS custom properties you can use to design stuff. It’s like a massive starting point for your styles. It’s saying custom property all the things, but in the way that we’re already used to with design tokens where they are a limited pre-determined number of choices.

The analogies are clear to people:

My guess is what will draw people to this is the beautiful defaults.

What it doesn’t do is prevent you from having to name things, which is something I know utility-class lovers really enjoy. Here, you’ll need to continue to use regular ol’ CSS selectors (like with named classes) to select things and style them as you “normally” would. But rather than hand-crafting your own values, you’re plucking values from these custom properties.

The whole base thing (you can view the source here) rolls in at 4.4kb across the wire (that’s what my DevTools showed, anyway). That doesn’t include the CSS you write to use the custom properties, but it’s a pretty tiny amount of overhead. There are additional PropPacks that increase the size (but thye are also super tiny), and if you’re worried about size, that’s what the whole just-in-time thing is about. You can play with that on StackBlitz.

Seems pretty sweet to me! I’d use it. I like that it’s ultimately just regular CSS, so there is nothing you can’t do. You’ll stay in good shape as CSS evolves.

CSS-Tricks

, , , ,

A Handy Little System for Animated Entrances in CSS

I love little touches that make a website feel like more than just a static document. What if web content wouldn’t just “appear” when a page loaded, but instead popped, slid, faded, or spun into place? It might be a stretch to say that movements like this are always useful, though in some cases they can draw attention to certain elements, reinforce which elements are distinct from one another, or even indicate a changed state. So, they’re not totally useless, either.

So, I put together a set of CSS utilities for animating elements as they enter into view. And, yes, this pure CSS. It not only has a nice variety of animations and variations, but supports staggering those animations as well, almost like a way of creating scenes.

You know, stuff like this:

Which is really just a fancier version of this:

We’ll go over the foundation I used to create the animations first, then get into the little flourishes I added, how to stagger animations, then how to apply them to HTML elements before we also take a look at how to do all of this while respecting a user’s reduced motion preferences.

The basics

The core idea involves adding a simple CSS @keyframes animation that’s applied to anything we want to animate on page load. Let’s make it so that an element fades in, going from opacity: 0 to opacity: 1 in a half second:

.animate {   animation-duration: 0.5s;   animation-name: animate-fade;   animation-delay: 0.5s;   animation-fill-mode: backwards; }  @keyframes animate-fade {   0% { opacity: 0; }   100% { opacity: 1; } }

Notice, too, that there’s an animation-delay of a half second in there, allowing the rest of the site a little time to load first. The animation-fill-mode: backwards is there to make sure that our initial animation state is active on page load. Without this, our animated element pops into view before we want it to.

If we’re lazy, we can call it a day and just go with this. But, CSS-Tricks readers aren’t lazy, of course, so let’s look at how we can make this sort of thing even better with a system.

Fancier animations

It’s much more fun to have a variety of animations to work with than just one or two. We don’t even need to create a bunch of new @keyframes to make more animations. It’s simple enough to create new classes where all we change is which frames the animation uses while keeping all the timing the same.

There’s nearly an infinite number of CSS animations out there. (See animate.style for a huge collection.) CSS filters, like blur(), brightness() and saturate() and of course CSS transforms can also be used to create even more variations.

But for now, let’s start with a new animation class that uses a CSS transform to make an element “pop” into place.

.animate.pop {   animation-duration: 0.5s;   animation-name: animate-pop;   animation-timing-function: cubic-bezier(.26, .53, .74, 1.48); }  @keyframes animate-pop {   0% {     opacity: 0;     transform: scale(0.5, 0.5);   }    100% {     opacity: 1;     transform: scale(1, 1);   } }

I threw in a little cubic-bezier() timing curve, courtesy of Lea Verou’s indispensable cubic-bezier.com for a springy bounce.

Adding delays

We can do better! For example, we can animate elements so that they enter at different times. This creates a stagger that makes for complex-looking motion without a complex amount of code.

This animation on three page elements using a CSS filter, CSS transform, and staggered by about a tenth of a second each, feels really nice:

All we did there was create a new class for each element that spaces when the elements start animating, using animation-delay values that are just a tenth of a second apart.

.delay-1 { animation-delay: 0.6s; }   .delay-2 { animation-delay: 0.7s; } .delay-3 { animation-delay: 0.8s; }

Everything else is exactly the same. And remember that our base delay is 0.5s, so these helper classes count up from there.

Respecting accessibility preferences

Let’s be good web citizens and remove our animations for users who have enabled their reduced motion preference setting:

@media screen and (prefers-reduced-motion: reduce) {   .animate { animation: none !important; } }

This way, the animation never loads and elements enter into view like normal. It’s here, though, that is worth a reminder that “reduced” motion doesn’t always mean “remove” motion.

Applying animations to HTML elements

So far, we’ve looked at a base animation as well as a slightly fancier one that we were able to make even fancier with staggered animation delays that are contained in new classes. We also saw how we can respect user motion preferences at the same time.

Even though there are live demos that show off the concepts, we haven’t actually walked though how to apply our work to HTML. And what’s cool is that we can use this on just about any element, whether its a div, span, article, header, section, table, form… you get the idea.

Here’s what we’re going to do. We want to use our animation system on three HTML elements where each element gets three classes. We could hard-code all the animation code to the element itself, but splitting it up gives us a little animation system we can reuse.

  • .animate: This is the base class that contains our core animation declaration and timing.
  • The animation type: We’ll use our “pop” animation from before, but we could use the one that fades in as well. This class is technically optional but is a good way to apply distinct movements.
  • .delay-<number>: As we saw earlier, we can create distinct classes that are used to stagger when the animation starts on each element, making for a neat effect. This class is also optional.

So our animated elements might now look like:

<h2 class="animate pop">One!</h2> <h2 class="animate pop delay-1">Two!</h2> <h2 class="animate pop delay-2">Three!</h2>

Let’s count them in!

Conclusion

Check that out: we went from a seemingly basic set of @keyframes and turned it into a full-fledged system for applying interesting animations for elements entering into view.

This is ridiculously fun, of course. But the big takeaway for me is how the examples we looked at form a complete system that can be used to create a baseline, different types of animations, staggered delays, and an approach for respecting user motion preferences. These, to me, are all the ingredients for a flexible system that easy to use, while giving us a lot with a little and without a bunch of extra cruft.

What we covered could indeed be a full animation library. But, of course, I did’t stop there and have my entire CSS file of animations in all its glory for you. There are several more types of animations in there, including 15 classes of different delays that can be used for staggering things. I’ve been using these on my own projects, but it’s still an early draft and I love feedback on it—so please enjoy and let me know what you think in the comments!

/* ========================================================================== Animation System by Neale Van Fleet from Rogue Amoeba ========================================================================== */ .animate {   animation-duration: 0.75s;   animation-delay: 0.5s;   animation-name: animate-fade;   animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);   animation-fill-mode: backwards; }  /* Fade In */ .animate.fade {   animation-name: animate-fade;   animation-timing-function: ease; }  @keyframes animate-fade {   0% { opacity: 0; }   100% { opacity: 1; } }  /* Pop In */ .animate.pop { animation-name: animate-pop; }  @keyframes animate-pop {   0% {     opacity: 0;     transform: scale(0.5, 0.5);   }   100% {     opacity: 1;     transform: scale(1, 1);   } }  /* Blur In */ .animate.blur {   animation-name: animate-blur;   animation-timing-function: ease; }  @keyframes animate-blur {   0% {     opacity: 0;     filter: blur(15px);   }   100% {     opacity: 1;     filter: blur(0px);   } }  /* Glow In */ .animate.glow {   animation-name: animate-glow;   animation-timing-function: ease; }  @keyframes animate-glow {   0% {     opacity: 0;     filter: brightness(3) saturate(3);     transform: scale(0.8, 0.8);   }   100% {     opacity: 1;     filter: brightness(1) saturate(1);     transform: scale(1, 1);   } }  /* Grow In */ .animate.grow { animation-name: animate-grow; }  @keyframes animate-grow {   0% {     opacity: 0;     transform: scale(1, 0);     visibility: hidden;   }   100% {     opacity: 1;     transform: scale(1, 1);   } }  /* Splat In */ .animate.splat { animation-name: animate-splat; }  @keyframes animate-splat {   0% {     opacity: 0;     transform: scale(0, 0) rotate(20deg) translate(0, -30px);     }   70% {     opacity: 1;     transform: scale(1.1, 1.1) rotate(15deg));   }   85% {     opacity: 1;     transform: scale(1.1, 1.1) rotate(15deg) translate(0, -10px);   }    100% {     opacity: 1;     transform: scale(1, 1) rotate(0) translate(0, 0);   } }  /* Roll In */ .animate.roll { animation-name: animate-roll; }  @keyframes animate-roll {   0% {     opacity: 0;     transform: scale(0, 0) rotate(360deg);   }   100% {     opacity: 1;     transform: scale(1, 1) rotate(0deg);   } }  /* Flip In */ .animate.flip {   animation-name: animate-flip;   transform-style: preserve-3d;   perspective: 1000px; }  @keyframes animate-flip {   0% {     opacity: 0;     transform: rotateX(-120deg) scale(0.9, 0.9);   }   100% {     opacity: 1;     transform: rotateX(0deg) scale(1, 1);   } }  /* Spin In */ .animate.spin {   animation-name: animate-spin;   transform-style: preserve-3d;   perspective: 1000px; }  @keyframes animate-spin {   0% {     opacity: 0;     transform: rotateY(-120deg) scale(0.9, .9);   }   100% {     opacity: 1;     transform: rotateY(0deg) scale(1, 1);   } }  /* Slide In */ .animate.slide { animation-name: animate-slide; }  @keyframes animate-slide {   0% {     opacity: 0;     transform: translate(0, 20px);   }   100% {     opacity: 1;     transform: translate(0, 0);   } }  /* Drop In */ .animate.drop {    animation-name: animate-drop;    animation-timing-function: cubic-bezier(.77, .14, .91, 1.25); }  @keyframes animate-drop { 0% {   opacity: 0;   transform: translate(0,-300px) scale(0.9, 1.1); } 95% {   opacity: 1;   transform: translate(0, 0) scale(0.9, 1.1); } 96% {   opacity: 1;   transform: translate(10px, 0) scale(1.2, 0.9); } 97% {   opacity: 1;   transform: translate(-10px, 0) scale(1.2, 0.9); } 98% {   opacity: 1;   transform: translate(5px, 0) scale(1.1, 0.9); } 99% {   opacity: 1;   transform: translate(-5px, 0) scale(1.1, 0.9); } 100% {   opacity: 1;   transform: translate(0, 0) scale(1, 1);   } }  /* Animation Delays */ .delay-1 {   animation-delay: 0.6s; } .delay-2 {   animation-delay: 0.7s; } .delay-3 {   animation-delay: 0.8s; } .delay-4 {   animation-delay: 0.9s; } .delay-5 {   animation-delay: 1s; } .delay-6 {   animation-delay: 1.1s; } .delay-7 {   animation-delay: 1.2s; } .delay-8 {   animation-delay: 1.3s; } .delay-9 {   animation-delay: 1.4s; } .delay-10 {   animation-delay: 1.5s; } .delay-11 {   animation-delay: 1.6s; } .delay-12 {   animation-delay: 1.7s; } .delay-13 {   animation-delay: 1.8s; } .delay-14 {   animation-delay: 1.9s; } .delay-15 {   animation-delay: 2s; }  @media screen and (prefers-reduced-motion: reduce) {   .animate {     animation: none !important;   } }

The post A Handy Little System for Animated Entrances in CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

System *Things

I think we’re all largely aware of named colors in CSS:

color: OldLace; background: rebeccapurple;

I guess you’d just call those “named colors” in CSS.

Those aren’t the only kind of named colors there are though. Some of them are a bit more fluid. Jim Nielsen was blowin’ minds the other day when he blogged about System Colors.

What I need is a way to say “hey browser, for my dropdown, use the same black (or white if in light mode) that you’re using for the background color of the document”. I need access to a variable of sorts that references the exact “black” the browser is using.

Then, via Thomas Steiner, I discovered there are literally CSS system colors. These aren’t colors that are (or at least attempt to be) the same across all browsers, but they are allowed to be set by “choices made by the user, the browser, or the OS.” So for example, Canvas is the “background of application content or documents.” Case in point: the background-color for dark mode is #1e1e1e in Safari and #121212 in Chrome. If you like that, meaning you’re leaning into what the browser thinks is a good color for things, then you can now access it through that Canvas keyword.

System colors! There are a bunch of them.

  • Canvas
  • CanvasText
  • LinkText
  • VisitedText
  • ActiveText
  • ButtonFace
  • ButtonText
  • ButtonBorder
  • Field
  • FieldText
  • Highlight
  • HighlightText
  • Mark
  • MarkText
  • GrayText

Not only do they change across browsers, they change when toggling between dark and light mode as long as you have CSS in place to support them…

html {   color-scheme: light dark; }

You’ll see them change when modes change. And you don’t have to use them for what they were designed for, I suppose:

So those are the system colors, but you can see right in that Pen that I’ve also used a system font: system-ui. Same vibe! It’s purposely fluid. It’s not going to be the same typeface across browsers and operating systems. Jim also covered this a while back. We used to replicate the idea with a big long stack of named fonts, but now CSS helps with it (in supporting browsers).

There are a bunch of them specced:

  • serif
  • sans-serif
  • monospace
  • system-ui
  • cursive
  • fantasy
  • emoji
  • math
  • fangsong
  • ui-serif
  • ui-sans-serif
  • ui-monospace
  • ui-rounded

Support seems scattered. For example, I could set this:

p {   font-family: ui-monospace, system-ui, fantasy; }

On my Mac, in Safari, I’d get SF Mono (ui-monospace). But in Chrome, ui-monospace doesn’t work so it would fall back to SF Pro (system-ui). In Firefox neither ui-monospace or system-ui work and I’d get Papyrus (fantasy). So font stacks are still important. It’s just funny to think about because these new system font keywords are almost like font stacks in and of themselves.

So there are system colors and system fonts — doesn’t that beg the question of what other system things there are?

Well, there are named font weights — like how font-weight: bold; is the same as 700, and bolder is just a bit more bold than the parent. But that doesn’t feel like a system-level thing where the system would want to take hold of that and do different things. But hey, maybe.

There are also named font sizes, like font-size: xx-small;. I could see systems wanting to get their hands on those values and adjust them to sizes that make sense contextually, but in a quick glance (comparing Chrome and iOS Safari), they compute to the same sizes.

Those named font size values don’t travel, either. I can’t do margin: large;. Well, I can, but it doesn’t do anything. So no real universal system sizes.

What about system icons? We do kinda have those in the form of emoji! We use the emoji knowing that different systems will render it differently and are generally fine with that as we know it will look consistent with that user’s platform.

The “Blue Book” emoji (via Emojipedia)

We could sort of think of inputs as “system inputs.” We know different browsers and platforms render input controls in very different ways, and that is how the spec intends it. To each their own.


The post System *Things appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]

Using Your Own Design System with KendoReact Components

Maybe you’ve already heard of (or even worked with!) KendoReact. It’s popped up in some of my day-to-day conversations, especially those about working with design systems and React. You could think of it as a component library like Bootstrap or Material Design, except the components in KendoReact are far more robust. These are interactive, state-driven components ready to start building full-blown UI’s right out of the gate (not to mention, if you want to use Bootstrap as the theme, you absolutely can).

Whenever you’re thinking about using a UI library, you need to think about the styling capabilities. Are you able to really express your brand with these? Were they meant to be styled? What is the styling experience going to be like?

Fortunately, KendoReact really makes styling a citizen of the entire UI library.

KendoReact is a collection of UI components for building sites. It’s a pretty massive one. Over 80 by my count, and that doesn’t include the child components of heavy lifters like the <Grid /> family.

Here’s one, the <DropDownList />, and just using the default theme (even that is optional):

If I want to style this, I don’t need any special proprietary skills, I can just use CSS. Here’s me forcing a whole new look onto it with different colors and fonts, with just some simple CSS:

But hey, maybe you want to do something a bit more systematized than cowboying some random override CSS. I don’t blame you. Good news: KendoReact themes are Sass-powered. So you can control a lot of the colorization and styling just by changing a few Sass variables.

They have a whole theme builder you can use right on their site that spits out exactly what you need. Say you want to start from their base theme and go from there, select the Default theme:

Then you can play with all the colors in the UI to your liking. Here’s me poking at a theme with some CSS-Tricks colors.

I can download that from the site which will give me the variables as a SCSS file that I can apply before the default theme in my build (there is a great tutorial covering how to do that over on the Telerik blog). Plus, it gives me the whole dang CSS file of the theme if I want to use it that way, which is simple and quick. Here’s me using their conversational chat widget with that theme:

Again, I can start with Bootstrap, I can start with Material, I can start with their default theme, or I can start from scratch. Styling is totally up to me. Each theme has its perks and, as you might expect, are super flexible as far as configuring colors, fonts, and other design elements.

If you really get into this, of course you’ll be consulting their docs and finding your way around there (it’s nice to know they have really comprehensive docs). It’s all pretty straightforward though, you’ll do great! If you need to get going building out a state-driven interactive interface quickly without sacrificing any customizability or power, you’ll find KendoReact is your friend.


The post Using Your Own Design System with KendoReact Components appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

Setting up and Customizing the Ant Design System in a Nuxt App

I don’t typically work with UI libraries because they can be cumbersome and hard to override, which can contribute to a bloated. However, Ant Design has recently gained some some of my affection because it’s easy to use, has extensible defaults, and features a delicate design.

Nuxt and Ant Design work well together, in part because of Nuxt’s code-splitting and tree-shaking abilities, not to mention Nuxt’s new static target deployment option. I can serve an app using Ant Design with great performance scores.

Combining the two was a little tricky and there isn’t a lot in the way of documentation for how to do it, so what follows are the steps you need to set it up. Let’s get started!

Install Ant.design 

The first step is installing the ant-design-vue package, along with Less.js and less-loader, which we will need to create our Less variables:

yarn add ant-design-vue less less-loader # or npm i ant-design-vue less less-loader

Now lets tell Nuxt to use it globally via a plugin. We’ll create a file called antd-ui.js:

import Vue from 'vue' import Antd from 'ant-design-vue/lib'  Vue.use(Antd)

You may notice that unlike the process outlined in the Ant Design getting started guide, we are not importing the global CSS file they mention. That’s because we’re going to manually import the base variable Less file instead so that we can override it. 

We have a few things to do in our nuxt.config.js file. First, let’s register the plugin we just made:

plugins: ["@/plugins/antd-ui"],

Next, we’re going to let webpack know we’d like to build Less:

build: {    loaders: {      less: {        lessOptions: {          javascriptEnabled: true,        },     },   }, }

Finally, we need to create a global stylesheet for our variables that imports Ant Design’s defaults as well as our overrides:

css: [   "~/assets/variables.less" ],

We can see that this file exists in a /assets folder, so let’s make it. We’ll create a file in there called variables.less, and import Ant Design’s Less variables:

@import '~ant-design-vue/dist/antd.less';

Below this line, there are myriad variables you can override. This is just a sampling. The rest of the variables are here, and you’ll need to include them by their @ and can change it to whatever you wish:

@primary-color: #1890ff; // primary color for all components @link-color: #1890ff; // link color @success-color: #52c41a; // success state color @warning-color: #faad14; // warning state color @error-color: #f5222d; // error state color @font-size-base: 14px; // major text font size @heading-color: rgba(0, 0, 0, 0.85); // heading text color @text-color: rgba(0, 0, 0, 0.65); // major text color @text-color-secondary: rgba(0, 0, 0, 0.45); // secondary text color @disabled-color: rgba(0, 0, 0, 0.25); // disable state color @border-radius-base: 4px; // major border radius @border-color-base: #d9d9d9; // major border color @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // major shadow for layers

We’re good to go! There’s no need to import what we need into every component because Nuxt will now take care of that. If you’d like to override very specific styles not included in the variables, you can find the associative classes and override them in your layouts/default.vue file as well.

Ant.design and Nuxt allow you a great framework for building apps very quickly and with ease. Enjoy!


The post Setting up and Customizing the Ant Design System in a Nuxt App appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

Designing a JavaScript Plugin System

WordPress has plugins. jQuery has plugins. Gatsby, Eleventy, and Vue do, too.

Plugins are a common feature of libraries and frameworks, and for a good reason: they allow developers to add functionality, in a safe, scalable way. This makes the core project more valuable, and it builds a community — all without creating an additional maintenance burden. What a great deal!

So how do you go about building a plugin system? Let’s answer that question by building one of our own, in JavaScript.

I’m using the word “plugin” but these things are sometimes called other names, like “extensions,” “add-ons,” or “modules.” Whatever you call them, the concept (and benefit) is the same.

Let’s build a plugin system

Let’s start with an example project called BetaCalc. The goal for BetaCalc is to be a minimalist JavaScript calculator that other developers can add “buttons” to. Here’s some basic code to get us started:

// The Calculator const betaCalc = {   currentValue: 0,      setValue(newValue) {     this.currentValue = newValue;     console.log(this.currentValue);   },      plus(addend) {     this.setValue(this.currentValue + addend);   },      minus(subtrahend) {     this.setValue(this.currentValue - subtrahend);   } }; 
 // Using the calculator betaCalc.setValue(3); // => 3 betaCalc.plus(3);     // => 6 betaCalc.minus(2);    // => 4

We’re defining our calculator as an object-literal to keep things simple. The calculator works by printing its result via console.log.

Functionality is really limited right now. We have a setValue method, which takes a number and displays it on the “screen.” We also have plus and minus methods, which will perform an operation on the currently displayed value.

It’s time to add more functionality. Let’s start by creating a plugin system.

The world’s smallest plugin system

We’ll start by creating a register method that other developers can use to register a plugin with BetaCalc. The job of this method is simple: take the external plugin, grab its exec function, and attach it to our calculator as a new method:

// The Calculator const betaCalc = {   // ...other calculator code up here 
   register(plugin) {     const { name, exec } = plugin;     this[name] = exec;   } };

And here’s an example plugin, which gives our calculator a “squared” button:

// Define the plugin const squaredPlugin = {   name: 'squared',   exec: function() {     this.setValue(this.currentValue * this.currentValue)   } }; 
 // Register the plugin betaCalc.register(squaredPlugin);

In many plugin systems, it’s common for plugins to have two parts:

  1. Code to be executed
  2. Metadata (like a name, description, version number, dependencies, etc.)

In our plugin, the exec function contains our code, and the name is our metadata. When the plugin is registered, the exec function is attached directly to our betaCalc object as a method, giving it access to BetaCalc’s this.

So now, BetaCalc has a new “squared” button, which can be called directly:

betaCalc.setValue(3); // => 3 betaCalc.plus(2);     // => 5 betaCalc.squared();   // => 25 betaCalc.squared();   // => 625

There’s a lot to like about this system. The plugin is a simple object-literal that can be passed into our function. This means that plugins can be downloaded via npm and imported as ES6 modules. Easy distribution is super important!

But our system has a few flaws.

By giving plugins access to BetaCalc’s this, they get read/write access to all of BetaCalc’s code. While this is useful for getting and setting the currentValue, it’s also dangerous. If a plugin was to redefine an internal function (like setValue), it could produce unexpected results for BetaCalc and other plugins. This violates the open-closed principle, which states that a software entity should be open for extension but closed for modification.

Also, the “squared” function works by producing side effects. That’s not uncommon in JavaScript, but it doesn’t feel great — especially when other plugins could be in there messing with the same internal state. A more functional approach would go a long way toward making our system safer and more predictable.

A better plugin architecture

Let’s take another pass at a better plugin architecture. This next example changes both the calculator and its plugin API:

// The Calculator const betaCalc = {   currentValue: 0,      setValue(value) {     this.currentValue = value;     console.log(this.currentValue);   },     core: {     'plus': (currentVal, addend) => currentVal + addend,     'minus': (currentVal, subtrahend) => currentVal - subtrahend   }, 
   plugins: {},     
   press(buttonName, newVal) {     const func = this.core[buttonName] || this.plugins[buttonName];     this.setValue(func(this.currentValue, newVal));   }, 
   register(plugin) {     const { name, exec } = plugin;     this.plugins[name] = exec;   } };    // Our Plugin const squaredPlugin = {    name: 'squared',   exec: function(currentValue) {     return currentValue * currentValue;   } }; 
 betaCalc.register(squaredPlugin); 
 // Using the calculator betaCalc.setValue(3);      // => 3 betaCalc.press('plus', 2); // => 5 betaCalc.press('squared'); // => 25 betaCalc.press('squared'); // => 625

We’ve got a few notable changes here.

First, we’ve separated the plugins from “core” calculator methods (like plus and minus), by putting them in their own plugins object. Storing our plugins in a plugin object makes our system safer. Now plugins accessing this can’t see the BetaCalc properties — they can only see properties of betaCalc.plugins.

Second, we’ve implemented a press method, which looks up the button’s function by name and then calls it. Now when we call a plugin’s exec function, we pass it the current calculator value (currentValue), and we expect it to return the new calculator value.

Essentially, this new press method converts all of our calculator buttons into pure functions. They take a value, perform an operation, and return the result. This has a lot of benefits:

  • It simplifies the API.
  • It makes testing easier (for both BetaCalc and the plugins themselves).
  • It reduces the dependencies of our system, making it more loosely coupled.

This new architecture is more limited than the first example, but in a good way. We’ve essentially put up guardrails for plugin authors, restricting them to only the kind of changes that we want them to make.

In fact, it might be too restrictive! Now our calculator plugins can only do operations on the currentValue. If a plugin author wanted to add advanced functionality like a “memory” button or a way to track history, they wouldn’t be able to.

Maybe that’s ok. The amount of power you give plugin authors is a delicate balance. Giving them too much power could impact the stability of your project. But giving them too little power makes it hard for them to solve their problems — in that case you might as well not have plugins.

What more could we do?

There’s a lot more we could do to improve our system.

We could add error handling to notify plugin authors if they forget to define a name or return a value. It’s good to think like a QA dev and imagine how our system could break so we can proactively handle those cases.

We could expand the scope of what a plugin can do. Currently, a BetaCalc plugin can add a button. But what if it could also register callbacks for certain lifecycle events — like when the calculator is about to display a value? Or what if there was a dedicated place for it to store a piece of state across multiple interactions? Would that open up some new use cases?

We could also expand plugin registration. What if a plugin could be registered with some initial settings? Could that make the plugins more flexible? What if a plugin author wanted to register a whole suite of buttons instead of a single one — like a “BetaCalc Statistics Pack”? What changes would be needed to support that?

Your plugin system

Both BetaCalc and its plugin system are deliberately simple. If your project is larger, then you’ll want to explore some other plugin architectures.

One good place to start is to look at existing projects for examples of successful plugin systems. For JavaScript, that could mean jQuery, Gatsby, D3, CKEditor, or others.

You may also want to be familiar with various JavaScript design patterns. (Addy Osmani has a book on the subject.)  Each pattern provides a different interface and degree of coupling, which gives you a lot of good plugin architecture options to choose from. Being aware of these options helps you better balance the needs of everyone who uses your project.

Besides the patterns themselves, there’s a lot of good software development principles you can draw on to make these kinds of decisions. I’ve mentioned a few along the way (like the open-closed principle and loose coupling), but some other relevant ones include the Law of Demeter and dependency injection.

I know it sounds like a lot, but you’ve gotta do your research. Nothing is more painful than making everyone rewrite their plugins because you needed to change the plugin architecture. It’s a quick way to lose trust and discourage people from contributing in the future.

Conclusion

Writing a good plugin architecture from scratch is difficult! You have to balance a lot of considerations to build a system that meets everyone’s needs. Is it simple enough? Powerful enough? Will it work long term?

It’s worth the effort though. Having a good plugin system helps everyone. Developers get the freedom to solve their problems. End users get a large number of opt-in features to choose from. And you get to grow an ecosystem and community around your project. It’s a win-win-win situation.


The post Designing a JavaScript Plugin System appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

System UIcons

This is a great collection of icons by Corey Ginnivan that’s both free and with no attribution required when you use them. The style is super simple. Each icon looks like older versions of the icons from macOS to me because they’re cute but not too cute.

Also? The icon picker UI is slick and looks something like this today:

Oh and also, as I was looking around Corey’s personal site I noticed this lovely UI effect when you scroll —each card stacks on top of each other:

Direct Link to ArticlePermalink


The post System UIcons appeared first on CSS-Tricks.

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

CSS-Tricks

,
[Top]

A Font-Like SVG Icon System for Vue

Managing a custom collection of icons in a Vue app can be challenging at times. An icon font is easy to use, but for customization, you have to rely on third-party font generators, and merge conflicts can be painful to resolve since fonts are binary files.

Using SVG files instead can eliminate those pain points, but how can we ensure they’re just as easy to use while also making it easy to add or remove icons?

Here is what my ideal icon system looks like:

  • To add icons, you just drop them into a designated icons folder. If you no longer need an icon, you simply delete it.
  • To use the rocket.svg icon in a template, the syntax is as simple as <svg-icon icon="rocket" />.
  • The icons can be scaled and colored using the CSS font-size and color properties (just like an icon font).
  • If multiple instances of the same icon appear on the page, the SVG code is not duplicated each time.
  • No webpack config editing is required.

This is what we will build by writing two small, single-file components. There are a few specific requirements for this implementation, though I’m sure many of you wizards out there could rework this system for other frameworks and build tools:

  • webpack: If you used the Vue CLI to scaffold your app, then you’re already using webpack.
  • svg-inline-loader: This allows us to load all of our SVG code and clean up portions we do not want. Go ahead and run npm install svg-inline-loader --save-dev from the terminal to get started.

The SVG sprite component

To meet our requirement of not repeating SVG code for each instance of an icon on the page, we need to build an SVG “sprite.” If you haven’t heard of an SVG sprite before, think of it as a hidden SVG that houses other SVGs. Anywhere we need to display an icon, we can copy it out of the sprite by referencing the id of the icon inside a <use> tag like this:

<svg><use xlink:href="#rocket" /></svg>

That little bit of code is essentially how our <SvgIcon> component will work, but let’s go ahead create the <SvgSprite> component first. Here is the entire SvgSprite.vue file; some of it may seem daunting at first, but I will break it all down.

<!-- SvgSprite.vue -->  <template>   <svg width="0" height="0" style="display: none;" v-html="$ options.svgSprite" /> </template>  <script> const svgContext = require.context(   '!svg-inline-loader?' +    'removeTags=true' + // remove title tags, etc.   '&removeSVGTagAttrs=true' + // enable removing attributes   '&removingTagAttrs=fill' + // remove fill attributes   '!@/assets/icons', // search this directory   true, // search subdirectories   /w+.svg$ /i // only include SVG files ) const symbols = svgContext.keys().map(path => {   // get SVG file content   const content = svgContext(path)    // extract icon id from filename   const id = path.replace(/^./(.*).w+$ /, '$ 1')   // replace svg tags with symbol tags and id attribute   return content.replace('<svg', `<symbol id="$ {id}"`).replace('svg>', 'symbol>') }) export default {   name: 'SvgSprite',   svgSprite: symbols.join('n'), // concatenate all symbols into $ options.svgSprite } </script>

In the template, our lone <svg> element has its content bound to $ options.svgSprite. In case you’re unfamiliar with $ options it contains properties that are directly attached to our Vue component. We could have attached svgSprite to our component’s data, but we don’t really need Vue to set up reactivity for this since our SVG loader is only going to run when our app builds.

In our script, we use require.context to retrieve all of our SVG files and clean them up while we’re at it. We invoke svg-inline-loader and pass it several parameters using syntax that is very similar to query string parameters. I’ve broken these up into multiple lines to make them easier to understand.

const svgContext = require.context(   '!svg-inline-loader?' +    'removeTags=true' + // remove title tags, etc.   '&removeSVGTagAttrs=true' + // enable removing attributes   '&removingTagAttrs=fill' + // remove fill attributes   '!@/assets/icons', // search this directory   true, // search subdirectories   /w+.svg$ /i // only include SVG files )

What we’re basically doing here is cleaning up the SVG files that live in a specific directory (/assets/icons) so that they’re in good shape to use anywhere we need them.

The removeTags parameter strips out tags that we do not need for our icons, such as title and style. We especially want to remove title tags since those can cause unwanted tooltips. If you would like to preserve any hard-coded styling in your icons, then add removingTags=title as an additional parameter so that only title tags are removed.

We also tell our loader to remove fill attributes, so that we can set our own fill colors with CSS later. It’s possible you will want to retain your fill colors. If that’s the case, then simply remove the removeSVGTagAttrs and removingTagAttrs parameters.

The last loader parameter is the path to our SVG icon folder. We then provide require.context with two more parameters so that it searches subdirectories and only loads SVG files.

In order to nest all of our SVG elements inside our SVG sprite, we have to convert them from <svg> elements into SVG <symbol> elements. This is as simple as changing the tag and giving each one a unique id, which we extract from the filename.

const symbols = svgContext.keys().map(path => {   // extract icon id from filename   const id = path.replace(/^./(.*).w+$ /, '$ 1')   // get SVG file content   const content = svgContext(path)   // replace svg tags with symbol tags and id attribute   return content.replace('<svg', `<symbol id="$ {id}"`).replace('svg>', 'symbol>') })

What do we do with this <SvgSprite> component? We place it on our page before any icons that depend on it. I recommend adding it to the top of the App.vue file.

<!-- App.vue --> <template>   <div id="app">     <svg-sprite /> <!-- ... -->

The icon component

Now let’s build the SvgIcon.vue component.

<!-- SvgIcon.vue -->  <template>   <svg class="icon" :class="{ 'icon-spin': spin }">     <use :xlink:href="`#$ {icon}`" />   </svg> </template>  <script> export default {   name: 'SvgIcon',   props: {     icon: {       type: String,       required: true,     },     spin: {       type: Boolean,       default: false,     },   }, } </script>  <style> svg.icon {   fill: currentColor;   height: 1em;   margin-bottom: 0.125em;   vertical-align: middle;   width: 1em; } svg.icon-spin {   animation: icon-spin 2s infinite linear; } @keyframes icon-spin {   from {     transform: rotate(0deg);   }   to {     transform: rotate(359deg);   } } </style>

This component is much simpler. As previously mentioned, we leverage the <use> tag to reference an id inside our sprite. That id comes from our component’s icon prop.

I’ve added a spin prop in there that toggles an .icon-spin class as an optional bit of animation, should we ever need. This could, for example, be useful for a loading spinner icon.

<svg-icon v-if="isLoading" icon="spinner" spin />

Depending on your needs, you may want to add additional props, such as rotate or flip. You could simply add the classes directly to the component without using props if you’d like.

Most of our component’s content is CSS. Other than the spinning animation, most of this is used to make our SVG icon act more like an icon font¹. To align the icons to the text baseline, I’ve found that applying vertical-align: middle, along with a bottom margin of 0.125em, works for most cases. We also set the fill attribute value to currentColor, which allows us to color the icon just like text.

<p style="font-size: 2em; color: red;">   <svg-icon icon="exclamation-circle" /><!-- This icon will be 2em and red. -->   Error! </p>

That’s it!  If you want to use the icon component anywhere in your app without having to import it into every component that needs it, be sure to register the component in your main.js file:

// main.js import Vue from 'vue' import SvgIcon from '@/components/SvgIcon.vue' Vue.component('svg-icon', SvgIcon) // ...

Final thoughts

Here are a few ideas for improvements, which I intentionally left out to keep this solution approachable:

  • Scale icons that have non-square dimensions to maintain their proportions
  • Inject the SVG sprite into the page without needing an additional component.
  • Make it work with vite, which is a new, fast (and webpack-free) build tool from Vue creator Evan You.
  • Leverage the Vue 3 Composition API.

If you want to quickly take these components for a spin, I’ve created a demo app based on the default vue-cli template. I hope this helps you develop an implementation that fits your app’s needs!


¹ If you’re wondering why we’re using SVG when we want it to behave like an icon font, then check out the classic post that pits the two against one another.


The post A Font-Like SVG Icon System for Vue appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

The Best Design System Tool is Slack

There’s a series questions I have struggled with for as long as I can remember. The questions have to do with how design systems work: Where should we document things? Do we make a separate app? Do we use a third-party tool to document our components? How should that tie into Figma or Sketch? What about written documentation? Should we invest a lot of time into making a giant Polaris-like wiki of how to build things?

The issue with all these tools and links and repositories is that it can become increasingly difficult to remember where to go for what kind of information. Designers should go here and engineers should go there — unless, of course, you’re an iOS engineer, then you need this special resource instead. It can be overwhelming and confusing for everyone that doesn’t live within the orbit of design systems drama and is just trying to ship a feature on time.

After years of struggling with these questions, I think my current advice to my past (and current) self is this: meet the people where they are. And where are most people asking questions about design systems, whether that’s a color variable or a component or a design pattern?

In Slack!

The other day I thought it would be neat to set up some Slackbot custom responses to do a rather simple thing. When someone types color me into a channel, I all the color variables and their hex values are pasted. That way, no one needs to learn a new tool or bookmark yet another link.

Here’s how it works.

We first have to open up the settings of the organization you’re in and click the “Customize” item in this dropdown:

That pops open a new tab with the “Customize your Workspace” settings. If you select “Slackbot” from the options, then you can then see all of the custom responses that have been set up already. From there, we can create a new response like this:

That n is what breaks things onto a new line so that I can now test it out in a chat with myself once I’ve saved this:

Because this takes up so much darn space, I also made separate answers for each color, like blue and purple. But all of this has me wondering: how else can we use Slack — or whatever chat app or communication tool — to extend the cause of good design systems work?

I bet there’s a ton of other things we can do to improve our lives within tools like this and make design systems work even easier.

The post The Best Design System Tool is Slack appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Chrome + System Fonts Snafu

There was just a bug late last year where system fonts (at least on Mac, I don’t know what the story was on other platforms) in Chrome appeared too thin and tracked-in at small sizes and too thick and tracked-out at larger sizes. That one was fixed, thankfully. But while it was a problem, it was the reason I gave up on system fonts for now and switched something else. A performance loss but aesthetic gain.

Now there is a new much worse bug, where the system font can’t be bolded. It’s not great, as a ton of sites roll with the system font stack as it has two major benefits: 1) it can help your site look like the operating system 2) it has great performance as the site doesn’t need to download/display and custom fonts.

Jon Henshaw wrote it up:

… the bug caught the attention of Adam Argyle, maker of VisBug and Chrome CSS Developer Advocate at Google. Argyle created a Chromium bug report, but the Chromium development team ultimately decided it wasn’t a blocker for releasing version 81. That resulted in sites like Coywolf not being able to use bold text for fonts that are larger than 16px (e.g., every heading).

The bug won’t be fixed in version 82 because the Chromium team announced that they’re skipping it, and will be releasing version 83 in mid-May instead. Argyle assured everyone on the original GitHub bug report that it would be fixed in version 83.

Above is Jon’s site. Andy Bell’s site got hit by it too.

So we’re looking at 4 weeks or so. Adam proposed a temporary fix of going Helvetica for now:

body {   font-family: -apple-system, Helvetica; }

I guess with -apple-system in there, older versions of Chrome/macOS still might be able to benefit from system fonts? Not sure.

That brings up a source of confusion for me. When I first heard of using system font stacks, there was -apple-system and BlinkMacSystemFont and you were supposed to use them in that order in the font stack. Then came along -system-ui, and that seemed to work well all by itself and that was nice as it was obviously less Mac-specific. But there is also system-ui (no starting dash), and that seems to do the same thing and I’m not sure which is correct. Now it looks like the plan is ui-sans-serif and friends (like ui-serif and ui-monospace). I like the idea, but I’d love to hear clarity from browser vendors on what the recommended use is. Are we in a spot like this?

/* Just a guess... */ body {   font-family:      ui-sans-serif,      system-ui,      -system-ui,      -apple-system,     BlinkMacSystemFont,     Roboto, Helvetica, Arial,      sans-serif,      "Apple Color Emoji"; }

Another observation from me… as I was trying to replicate this on Chrome 81, at first I was like “weird, works for me”, because I was trying out the bolding on default 16px text. I noticed that it was when the font was 20px or bigger the problem kicked in:

Bramus has an alternative fix idea: use Inter.

The post Chrome + System Fonts Snafu appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]