Introducing Shoelace, a Framework-Independent Component-Based UX Library

This is a post about Shoelace, a component library by Cory LaViska, but with a twist. It defines all your standard UX components: tabs, modals, accordions, auto-completes, and much, much more. They look beautiful out of the box, are accessible, and fully customizable. But rather than creating these components in React, or Solid, or Svelte, etc., it creates them with Web Components; this means you can use them with any framework.

Some preliminary things

Web Components are great, but there’s currently a few small hitches to be aware of.

React

I said they work in any JavaScript framework, but as I’ve written before, React’s support for Web Components is currently poor. To address this, Shoelace actually created wrappers just for React.

Another option, which I personally like, is to create a thin React component that accepts the tag name of a Web Component and all of its attributes and properties, then does the dirty work of handling React’s shortcomings. I talked about this option in a previous post. I like this solution because it’s designed to be deleted. The Web Component interoperability problem is currently fixed in React’s experimental branch, so once that’s shipped, any thin Web Component-interoperable component you’re using could be searched, and removed, leaving you with direct Web Component usages, without any React wrappers.

Server-Side Rendering (SSR)

Support for SSR is also poor at the time of this writing. In theory, there’s something called Declarative Shadow DOM (DSD) which would enable SSR. But browser support is minimal, and in any event, DSD actually requires server support to work right, which means Next, Remix, or whatever you happen to use on the server will need to become capable of some special handling.

That said, there are other ways to get Web Components to just work with a web app that’s SSR’d with something like Next. The short version is that the scripts registering your Web Components need to run in a blocking script before your markup is parsed. But that’s a topic for another post.

Of course, if you’re building any kind of client-rendered SPA, this is a non-issue. This is what we’ll work with in this post.

Let’s start

Since I want this post to focus on Shoelace and on its Web Component nature, I’ll be using Svelte for everything. I’ll also be using this Stackblitz project for demonstration. We’ll build this demo together, step-by-step, but feel free to open that REPL up anytime to see the end result.

I’ll show you how to use Shoelace, and more importantly, how to customize it. We’ll talk about Shadow DOMs and which styles they block from the outside world (as well as which ones they don’t). We’ll also talk about the ::part CSS selector — which may be entirely new to you — and we’ll even see how Shoelace allows us to override and customize its various animations.

If you find you like Shoelace after reading this post and want to try it in a React project, my advice is to use a wrapper like I mentioned in the introduction. This will allow you to use any of Shoelace’s components, and it can be removed altogether once React ships the Web Component fixes they already have (look for that in version 19).

Introducing Shoelace

Shoelace has fairly detailed installation instructions. At its most simple, you can dump <script> and <style> tags into your HTML doc, and that’s that. For any production app, though, you’ll probably want to selectively import only what you want, and there are instructions for that, too.

With Shoelace installed, let’s create a Svelte component to render some content, and then go through the steps to fully customize it. To pick something fairly non-trivial, I went with the tabs and a dialog (commonly referred to as a modal) components. Here’s some markup taken largely from the docs:

<sl-tab-group>
  <sl-tab slot="nav" panel="general">General</sl-tab>
  <sl-tab slot="nav" panel="custom">Custom</sl-tab>
  <sl-tab slot="nav" panel="advanced">Advanced</sl-tab>
  <sl-tab slot="nav" panel="disabled" disabled>Disabled</sl-tab>

  <sl-tab-panel name="general">This is the general tab panel.</sl-tab-panel>
  <sl-tab-panel name="custom">This is the custom tab panel.</sl-tab-panel>
  <sl-tab-panel name="advanced">This is the advanced tab panel.</sl-tab-panel>
  <sl-tab-panel name="disabled">This is a disabled tab panel.</sl-tab-panel>
</sl-tab-group>

<sl-dialog no-header label="Dialog">
  Hello World!
  <button slot="footer" variant="primary">Close</button>
</sl-dialog>

<br />
<button>Open Dialog</button>

This renders some nice, styled tabs. The underline on the active tab even animates nicely, and slides from one active tab to the next.

Four horizontal tab headings with the first active in blue with placeholder content contained in a panel below.
Default tabs in Shoelace

I won’t waste your time running through every inch of the APIs that are already well-documented on the Shoelace website. Instead, let’s look into how best to interact with, and fully customize these Web Components.

Interacting with the API: methods and events

Calling methods and subscribing to events on a Web Component might be slightly different than what you’re used to with your normal framework of choice, but it’s not too complicated. Let’s see how.

Tabs

The tabs component (<sl-tab-group>) has a show method, which manually shows a particular tab. In order to call this, we need to get access to the underlying DOM element of our tabs. In Svelte, that means using bind:this. In React, it’d be a ref. And so on. Since we’re using Svelte, let’s declare a variable for our tabs instance:

<script>
  let tabs;
</script>

…and bind it:

<sl-tab-group bind:this="{tabs}"></sl-tab-group>

Now we can add a button to call it:

<button on:click={() => tabs.show("custom")}>Show custom</button>

It’s the same idea for events. There’s a sl-tab-show event that fires when a new tab is shown. We could use addEventListener on our tabs variable, or we can use Svelte’s on:event-name shortcut.

<sl-tab-group bind:this={tabs} on:sl-tab-show={e => console.log(e)}>

That works and logs the event objects as you show different tabs.

Event object meta shown in DevTools.

Typically we render tabs and let the user click between them, so this work isn’t usually even necessary, but it’s there if you need it. Now let’s get the dialog component interactive.

Dialog

The dialog component (<sl-dialog>) takes an open prop which controls whether the dialog is… open. Let’s declare it in our Svelte component:

<script>
  let tabs;
  let open = false;
</script>

It also has an sl-hide event for when the dialog is hidden. Let’s pass our open prop and bind to the hide event so we can reset it when the user clicks outside of the dialog content to close it. And let’s add a click handler to that close button to set our open prop to false, which would also close the dialog.

<sl-dialog no-header {open} label="Dialog" on:sl-hide={() => open = false}>
  Hello World!
  <button slot="footer" variant="primary" on:click={() => open = false}>Close</button>
</sl-dialog>

Lastly, let’s wire up our open dialog button:

<button on:click={() => (open = true)}>Open Dialog</button>

And that’s that. Interacting with a component library’s API is more or less straightforward. If that’s all this post did, it would be pretty boring.

But Shoelace — being built with Web Components — means that some things, particularly styles, will work a bit differently than we might be used to.

Customize all the styles!

As of this writing, Shoelace is still in beta and the creator is considering changing some default styles, possibly even removing some defaults altogether so they’ll no longer override your host application’s styles. The concepts we’ll cover are relevant either way, but don’t be surprised if some of the Shoelace specifics I mention are different when you go to use it.

As nice as Shoelace’s default styles are, we might have our own designs in our web app, and we’ll want our UX components to match. Let’s see how we’d go about that in a Web Components world.

We won’t try to actually improve anything. The Shoelace creator is a far better designer than I’ll ever be. Instead, we’ll just look at how to change things, so you can adapt to your own web apps.

A quick tour of Shadow DOMs

Take a peek at one of those tab headers in your DevTools; it should look something like this:

The tabs component markup shown in DevTools.

Our tab element has created a div container with a .tab and .tab--active class, and a tabindex, while also displaying the text we entered for that tab. But notice that it’s sitting inside of a shadow root. This allows Web Component authors to add their own markup to the Web Component while also providing a place for the content we provide. Notice the <slot> element? That basically means “put whatever content the user rendered between the Web Component tags here.”

So the <sl-tab> component creates a shadow root, adds some content to it to render the nicely-styled tab header along with a placeholder (<slot>) that renders our content inside.

Encapsulated styles

One of the classic, more frustrating problems in web development has always been styles cascading to places where we don’t want them. You might worry that any style rules in our application which specify something like div.tab would interfere with these tabs. It turns out this isn’t a problem; shadow roots encapsulate styles. Styles from outside the shadow root do not affect what’s inside the shadow root (with some exceptions which we’ll talk about), and vice versa.

The exceptions to this are inheritable styles. You, of course, don’t need to apply a font-family style for every element in your web app. Instead, you can specify your font-family once, on :root or html and have it inherit everywhere beneath it. This inheritance will, in fact, pierce the shadow root as well.

CSS custom properties (often called “css variables”) are a related exception. A shadow root can absolutely read a CSS property that is defined outside the shadow root; this will become relevant in a moment.

The ::part selector

What about styles that don’t inherit. What if we want to customize something like cursor, which doesn’t inherit, on something inside of the shadow root. Are we out of luck? It turns out we’re not. Take another look at the tab element image above and its shadow root. Notice the part attribute on the div? That allows you to target and style that element from outside the shadow root using the ::part selector. We’ll walk through an example is a bit.

Overriding Shoelace styles

Let’s see each of these approaches in action. As of now, a lot of Shoelace styles, including fonts, receive default values from CSS custom properties. To align those fonts with your application’s styles, override the custom props in question. See the docs for info on which CSS variables Shoelace is using, or you can simply inspect the styles in any given element in DevTools.

Inheriting styles through the shadow root

Open the app.css file in the src directory of the StackBlitz project. In the :root section at the bottom, you should see a letter-spacing: normal; declaration. Since the letter-spacing property is inheritable, try setting a new value, like 2px. On save, all content, including the tab headers defined in the shadow root, will adjust accordingly.

Four horizontal tab headers with the first active in blue with plqceholder content contained in a panel below. The text is slightly stretched with letter spacing.

Overwriting Shoelace CSS variables

The <sl-tab-group> component reads an --indicator-color CSS custom property for the active tab’s underline. We can override this with some basic CSS:

sl-tab-group {
  --indicator-color: green;
}

And just like that, we now have a green indicator!

Four horizontal tab headers with the first active with blue text and a green underline.

Querying parts

In the version of Shoelace I’m using right now (2.0.0-beta.83), any non-disabled tab has a pointer cursor. Let’s change that to a default cursor for the active (selected) tab. We already saw that the <sl-tab> element adds a part="base" attribute on the container for the tab header. Also, the currently selected tab receives an active attribute. Let’s use these facts to target the active tab, and change the cursor:

sl-tab[active]::part(base) {
  cursor: default;
}

And that’s that!

Customizing animations

For some icing on the metaphorical cake, let’s see how Shoelace allows us to customize animations. Shoelace uses the Web Animations API, and exposes a setDefaultAnimation API to control how different elements animate their various interactions. See the docs for specifics, but as an example, here’s how you might change Shoelace’s default dialog animation from expanding outward, and shrinking inward, to instead animate in from the top, and drop down while hiding.

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";

setDefaultAnimation("dialog.show", {
  keyframes: [
    { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
  ],
  options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});
setDefaultAnimation("dialog.hide", {
  keyframes: [
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
    { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },
  ],
  options: { duration: 200, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});

That code is in the App.svelte file. Comment it out to see the original, default animation.

Wrapping up

Shoelace is an incredibly ambitious component library that’s built with Web Components. Since Web Components are framework-independent, they can be used in any project, with any framework. With new frameworks starting to come out with both amazing performance characteristics, and also ease of use, the ability to use quality user experience widgets which aren’t tied to any one framework has never been more compelling.


Introducing Shoelace, a Framework-Independent Component-Based UX Library originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

The Basics of Remix

You’ve probably heard a lot of hype around one of the newest kids on the framework block, Remix. It may be surprising that it got its start back in 2019, but it was originally only available as a subscription-based premium framework. In 2021, the founders raised seed funding and open sourced the framework to let users start using Remix for free. The floodgates opened and everyone seems to be talking about it, good or bad. Let’s dive in and look at some of the basics of Remix.

Remix is a server “edge” first JavaScript framework. It uses React, at least for now, for the front end and prioritizes server-side rendering the application on the edge. Platforms can take the server-side code and run it as serverless or edge functions making it cheaper than a traditional server and putting it closer to your users. The Remix founders like to call it a “center stack” framework because it adapts the requests and responses made between the server and the client for the platform it is being run on.

Deploying Remix

Because Remix requires a server, let’s talk about how you can deploy it. Remix does not provide the server itself — you bring the server — allowing it to be run in any Node.js or Deno environment, including Netlify Edge and DigitalOcean’s App Platform. Remix itself is a compiler, a program that translates the requests for the platform it is running on. This process uses esbuild to create handlers for the requests to the server. The HTTP handlers it uses are built on the Web Fetch API and are ran on the server by adapting them for the platform they will be deployed to.

Remix stacks

Remix stacks are projects that have some common tools that come preconfigured for you. There are three official stacks that are maintained by the Remix team and they are all named after musical genres. There is also a number of community Remix stacks including the K-Pop Stack created by the Templates Team at Netlify. This stack is a powerhouse and includes a Supabase database and authentication, Tailwind for styling, Cypress end-to-end testing, Prettier code formatting, ESLint linting, and TypeScript static typing. Check out Tara Manicsic’s post on deploying the K-Pop Stack.

Caching routes

Even though Remix requires a server, it can still take advantage of the Jamstack benefits by caching routes. A static site or static site generation (SSG) is when all of your content is rendered at build time and stays static until another rebuild. The content is pre-generated and can be put on a CDN. This provides many benefits and speedy site loads for the end user. However, Remix does not do typical SSG like other popular React frameworks, including Next.js and Gatsby. To get the some of the benefits of SSG, you can use the native Cache-Control HTTP header in a Remix headers function to cache a particular route or directly in the root.tsx file.

[[headers]]
  for = "/build/*"
  [headers.values]
    "Cache-Control" = "public, max-age=31536000, s-maxage=31536000"

Then add in your headers function where you want it. This caches for one hour:

export function headers() {
  return {
    "Cache-Control": "public, s-maxage=360",
  };
};

Remixing routing

A lot of frameworks have leaned into routing based on file systems. This is a technique where a designated folder is used to define routes for your application. They typically have special syntax for declaring dynamic routes and endpoints. The biggest difference currently between Remix and other popular frameworks is the ability to use nested routing.

Every Remix app starts with the root.tsx file. This is where the entire base of the app is rendered. You’ll find some of the common HTML layout here like the <html> tag, the <head> tag, and then the <body> tag with the components needed to render the app. The one thing to point out here is the <Scripts> component is what enables JavaScript on the site; some things will work without it, but not everything. The root.tsx file acts as a parent layout for everything inside of the routes directory, everything in routes is rendered where the <Outlet/> component is in root.tsx. This is the base of nested routing in Remix.

Nested routing

Not only was Remix founded by some of the team from React Router, it also uses React Router. In fact, they are bringing some of the good things about Remix back to React Router. A complex problem that the maintainers of Next.js and SvelteKit are trying to solve right now is nested routing.

Nested routing is unlike traditional routing. Where a new route would take a user to a new page, each nested route is a separate section of the same page. It allows for separation of concerns by keeping business logic associated with only the files that need it. Remix is able to handle errors localized to only the section of the page the nested route is at. The other routes on the page are still usable and the route that broke can provide relevant context to the error without the entire page crashing.

Remix does this when a root file in app/routes is named the same as a directory of files that will load inside of the base file. The root file becomes a layout for the files in the directory by using an <Outlet /> component to tell Remix where to load the other routes.

Outlet component

The <Outlet /> Component is a signal to Remix for where it should render content for nested routes. It’s put in the file at the root of the app/routes directory with the same name as the nested routes. The following code goes in a app/routes/about.tsx file and includes the outlet for the files inside app/routes/about folder:

import { Outlet } from "@remix-run/react";

export default function About() {
  return (
    <>
      <section>
        I am the parent layout. I will be on any page inside of my named directory.
      </section>
      { /* All of my children, the files in the named directory, will go here. */ }
      <Outlet />
    </>
  )
}

Folder structure

Any file in the app/routes/ directory becomes a route at the URL of its name. A directory can also be added with an index.tsx file.

app/
├── routes/
│   │
│   └── blog
|   |   ├── index.tsx ## The /blog route
│   └── about.tsx  ## The /about route
│   ├── index.tsx  ## The / or home route
└── root.tsx

If a route has the same name as a directory, the named file becomes a layout file for the files inside the directory and the layout file needs an Outlet component to place the nested route in.

app/
├── routes/
│   │
│   └── about
│   │   ├── index.tsx
│   ├── about.tsx ## this is a layout for /about/index.tsx
│   ├── index.tsx
└── root.tsx

Layouts can also be created by prefixing them with a double underscore (__).

app/
├── routes/
│   │
│   └── about
│   │   ├── index.tsx
│   ├── index.tsx
│   ├── about.tsx
│   ├── __blog.tsx ## this is also a layout
└── root.tsx

https://your-url.com/about will still render the app/routes/about.tsx file, but will also render whatever is in app/routes/about/index.tsx where the Outlet component is in the markup of app/routes/about.tsx.

Dynamic Routes

A dynamic route is a route that changes based on information in the url. That may be a name of a blog post or a customer id, but no matter what it is the $ syntax added to the front of the route signals to Remix that it is dynamic. The name doesn’t matter other than the $ prefix.

app/
├── routes/
│   │
│   └── about
│   │   ├── $id.tsx
│   │   ├── index.tsx
│   ├── about.tsx ## this is a layout for /about/index.tsx
│   ├── index.tsx
└── root.tsx

Fetch that data!

Since Remix renders all of its data on the server, you don’t see a lot of the things that have become the standard of a React app, like useState() and useEffect() hooks, in Remix. There is less need for client-side state since it has already been evaluated on the server.

It also doesn’t matter what type of server you use for fetching data. Since Remix sits between the request and response and translates it appropriately, you can use the standard Web Fetch API. Remix does this in a loader function that only runs on the server and uses the useLoaderData() hook to render the data in the component. Here’s an example using the Cat as a Service API to render a random cat image.

import { Outlet, useLoaderData } from '@remix-run/react'

export async function loader() {
  const response = await fetch('<https://cataas.com/cat?json=true>')
  const data = await response.json()
  return {
    data
  }
}

export default function AboutLayout() {
  const cat = useLoaderData<typeof loader>()
  return (
    <>
      <img
        src={`https://cataas.com/cat/${cat}`}
        alt="A random cat."
      />
      <Outlet />
    </>
  )
}

Route parameters

In dynamic routes, routes prefixed with $ need to be able to access the URL parameter to handle that data that should be rendered. The loader function has access to these through a params argument.

import { useLoaderData } from '@remix-run/react'
import type { LoaderArgs } from '@remix-run/node'

export async function loader({ params }: LoaderArgs) {
  return {
      params
  }
}

export default function AboutLayout() {
  const { params } = useLoaderData<typeof loader>()
  return <p>The url parameter is {params.tag}.</p>
}

Other Remix functions

Remix has a few other helper functions that add extra functionality to normal HTML elements and attributes in the route module API. Each route can define its own of these types of functions.

Action function

An action function allows you to add extra functionality to a form action using the standard web FormData API.

export async function action({ request }) {
  const body = await request.formData();
  const todo = await fakeCreateTodo({
      title: body.get("title"),
  });
  return redirect(`/todos/${todo.id}`);
}

Headers function

Any HTTP standard headers can go in a headers function. Because each route can have a header, to avoid conflicts with nested routes, the deepest route — or the URL with the most forward slashes (/) — wins. You can also get the headers passed through, actionHeaders, loaderHeaders, or parentHeaders

export function headers({
  actionHeaders,
  loaderHeaders,
  parentHeaders,
}) {
  return {
"Cache-Control": loaderHeaders.get("Cache-Control"),
  };
}

Meta function

This function will set the meta tags for the HTML document. One is set in the root.tsx file by default, but they can be updated for each route.

export function meta() {
  return {
    title: "Your page title",
    description: "A new description for each route.",
  };
};

HTML link elements live in the <head> tag of an HTML document and they import CSS, among other things. The links function, not to be confused with the <Link /> component, allows you to only import things in the routes that need them. So, for example, CSS files can be scoped and only imported on the routes that need those specific files. The link elements are returned from a links() function as an array of objects and can either be a HtmlLinkDescriptor from the link API or a PageLinkDescriptor that can prefetch the data for a page.

export function links() {
  return [
    // add a favicon
    {
      rel: "icon",
      href: "/favicon.png",
      type: "image/png",
    },
    // add an external stylesheet
    {
      rel: "stylesheet",
      href: "<https://example.com/some/styles.css>",
      crossOrigin: "true",
    },
    // add a local stylesheet,
    { rel: "stylesheet", href: stylesHref },

    // prefetch a page's data
    { page: "/about/community" }
  ]
}

Linking between routes

Remix provides a component to go between the different routes in your app called <Link/>. To get client-side routing, use the <Link to="">Name</Link> component instead of <a href="">Name</a>. The <Link /> component also takes a prop of prefetch with accepts none by default, intent to prefetch the data if Remix detects the user hovers or focuses the link, or render which will fetch the route’s data as soon as the link is rendered.

import { Link } from "@remix-run/react";

export default function Nav() {
  return (
    <nav>
      <Link to="/">Home</Link>{" "}
      <Link to="/about">About</Link>{" "}
      <Link to="/about/community" prefetch="intent">Community</Link>
    </nav>
  );
}

Next steps

Now you know the basics of Remix and you’re ready to get started actually building applications, right? Remix provides a Jokes app and a Blog tutorial to get you started implementing this basic knowledge. You can also start from scratch and create a brand new Remix app. Or if you are ready to dive in, give the K-Pop Stack a try. I have really enjoyed my time with Remix and love the focus on web standards and bringing it back to the basics. Now it’s your turn to start creating!


The Basics of Remix originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Flutter vs. React Native in 2022: Detailed Framework Comparison

Native applications work smoothly on mobile devices. Their graphic transitions take less time for UI rendering compared to the cross-platforming. The reason is simple: their code can communicate straight to the mobile OS core, enabling UI methods.

In the meantime, the native mobile platforms (OS) are written in different languages. For instance, Android OS is a mix of Java, C, and C++. In comparison, the iOS platform is built with Objective C and Swift. So, native mobile development requires the knowledge of a few languages like Swift and Java simultaneously. That's a big deal, as not many companies could afford to hire developers for each platform.

How to Make a Component That Supports Multiple Frameworks in a Monorepo

Your mission — should you decide to accept it — is to build a Button component in four frameworks, but, only use one button.css file!

This idea is very important to me. I’ve been working on a component library called AgnosticUI where the purpose is building UI components that aren’t tied to any one particular JavaScript framework. AgnosticUI works in React, Vue 3, Angular, and Svelte. So that’s exactly what we’ll do today in this article: build a button component that works across all these frameworks.

The source code for this article is available on GitHub on the the-little-button-that-could-series branch.

Table of contents

Why a monorepo?

We’re going to set up a tiny Yarn workspaces-based monorepo. Why? Chris actually has a nice outline of the benefits in another post. But here’s my own biased list of benefits that I feel are relevant for our little buttons endeavor:

Coupling

We’re trying to build a single button component that uses just one button.css file across multiple frameworks. So, by nature, there’s some purposeful coupling going on between the various framework implementations and the single-source-of-truth CSS file. A monorepo setup provides a convenient structure that facilitates copying our single button.css component into various framework-based projects.

Workflow

Let’s say the button needs a tweak — like the “focus-ring” implementation, or we screwed up the use of aria in the component templates. Ideally, we’d like to correct things in one place rather than making individual fixes in separate repositories.

Testing

We want the convenience of firing up all four button implementations at the same time for testing. As this sort of project grows, it’s safe to assume there will be more proper testing. In AgnosticUI, for example, I’m currently using Storybook and often kick off all the framework Storybooks, or run snapshot testing across the entire monorepo.

I like what Leonardo Losoviz has to say about the monorepo approach. (And it just so happens to align with with everything we’ve talked about so far.)

I believe the monorepo is particularly useful when all packages are coded in the same programming language, tightly coupled, and relying on the same tooling.

Setting up

Time to dive into code — start by creating a top-level directory on the command-line to house the project and then cd into it. (Can’t think of a name? mkdir buttons && cd buttons will work fine.)

First off, let’s initialize the project:

$ yarn init
yarn init v1.22.15
question name (articles): littlebutton
question version (1.0.0): 
question description: my little button project
question entry point (index.js): 
question repository url: 
question author (Rob Levin): 
question license (MIT): 
question private: 
success Saved package.json

That gives us a package.json file with something like this:

{
  "name": "littlebutton",
  "version": "1.0.0",
  "description": "my little button project",
  "main": "index.js",
  "author": "Rob Levin",
  "license": "MIT"
}

Creating the baseline workspace

We can set the first one up with this command:

mkdir -p ./littlebutton-css

Next, we need to add the two following lines to the monorepo’s top-level package.json file so that we keep the monorepo itself private. It also declares our workspaces:

// ...
"private": true,
"workspaces": ["littlebutton-react", "littlebutton-vue", "littlebutton-svelte", "littlebutton-angular", "littlebutton-css"]

Now descend into the littlebutton-css directory. We’ll again want to generate a package.json with yarn init. Since we’ve named our directory littlebutton-css (the same as how we specified it in our workspaces in package.json) we can simply hit the Return key and accept all the prompts:

$ cd ./littlebutton-css && yarn init
yarn init v1.22.15
question name (littlebutton-css): 
question version (1.0.0): 
question description: 
question entry point (index.js): 
question repository url: 
question author (Rob Levin): 
question license (MIT): 
question private: 
success Saved package.json

At this point, the directory structure should look like this:

├── littlebutton-css
│   └── package.json
└── package.json

We’ve only created the CSS package workspace at this point as we’ll be generating our framework implementations with tools like vite which, in turn, generate a package.json and project directory for you. We will have to remember that the name we choose for these generated projects must match the name we’ve specified in the package.json for our earlier workspaces to work.

Baseline HTML & CSS

Let’s stay in the ./littlebutton-css workspace and create our simple button component using vanilla HTML and CSS files.

touch index.html ./css/button.css

Now our project directory should look like this:

littlebutton-css
├── css
│   └── button.css
├── index.html
└── package.json

Let’s go ahead and connect some dots with some boilerplate HTML in ./index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>The Little Button That Could</title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="css/button.css">
</head>
<body>
  <main>
    <button class="btn">Go</button>
  </main>
</body>
</html>

And, just so we have something visual to test, we can add a little color in ./css/button.css:

.btn {
  color: hotpink;
}
A mostly unstyled button with hot-pink text from the monorepo framework.

Now open up that index.html page in the browser. If you see an ugly generic button with hotpink text… success!

Framework-specific workspaces

So what we just accomplished is the baseline for our button component. What we want to do now is abstract it a bit so it’s extensible for other frameworks and such. For example, what if we want to use the button in a React project? We’re going to need workspaces in our monorepo for each one. We’ll start with React, then follow suit for Vue 3, Angular, and Svelte.

React

We’re going to generate our React project using vite, a very lightweight and blazingly fast builder. Be forewarned that if you attempt to do this with create-react-app, there’s a very good chance you will run into conflicts later with react-scripts and conflicting webpack or Babel configurations from other frameworks, like Angular.

To get our React workspace going, let’s go back into the terminal and cd back up to the top-level directory. From there, we’ll use vite to initialize a new project — let’s call it littlebutton-react — and, of course, we’ll select react as the framework and variant at the prompts:

$ yarn create vite
yarn create v1.22.15
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Installed "create-vite@2.6.6" with binaries:
      - create-vite
      - cva
✔ Project name: … littlebutton-react
✔ Select a framework: › react
✔ Select a variant: › react

Scaffolding project in /Users/roblevin/workspace/opensource/guest-posts/articles/littlebutton-react...

Done. Now run:

  cd littlebutton-react
  yarn
  yarn dev

✨  Done in 17.90s.

We initialize the React app with these commands next:

cd littlebutton-react
yarn
yarn dev

With React installed and verified, let’s replace the contents of src/App.jsx to house our button with the following code:

import "./App.css";

const Button = () => {
  return <button>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button />
    </div>
  );
}

export default App;

Now we’re going to write a small Node script that copies our littlebutton-css/css/button.css right into our React application for us. This step is probably the most interesting one to me because it’s both magical and ugly at the same time. It’s magical because it means our React button component is truly deriving its styles from the same CSS written in the baseline project. It’s ugly because, well, we are reaching up out of one workspace and grabbing a file from another. ¯\_(ツ)_/¯

Add the following little Node script to littlebutton-react/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
fs.writeFileSync("./src/button.css", css, "utf8");

Let’s place a node command to run that in a package.json script that happens before the dev script in littlebutton-react/package.json. We’ll add a syncStyles and update the dev to call syncStyles before vite:

"syncStyles": "node copystyles.js",
"dev": "yarn syncStyles && vite",

Now, anytime we fire up our React application with yarn dev, we’ll first be copying the CSS file over. In essence, we’re “forcing” ourselves to not diverge from the CSS package’s button.css in our React button.

But we want to also leverage CSS Modules to prevent name collisions and global CSS leakage, so we have one more step to do to get that wired up (from the same littlebutton-react directory):

touch src/button.module.css

Next, add the following to the new src/button.module.css file:

.btn {
  composes: btn from './button.css';
}

I find composes (also known as composition) to be one of the coolest features of CSS Modules. In a nutshell, we’re copying our HTML/CSS version of button.css over wholesale then composing from our one .btn style rule.

With that, we can go back to our src/App.jsx and import the CSS Modules styles into our React component with this:

import "./App.css";
import styles from "./button.module.css";

const Button = () => {
  return <button className={styles.btn}>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button />
    </div>
  );
}

export default App;

Whew! Let’s pause and try to run our React app again:

yarn dev

If all went well, you should see that same generic button, but with hotpink text. Before we move on to the next framework, let’s move back up to our top-level monorepo directory and update its package.json:

{
  "name": "littlebutton",
  "version": "1.0.0",
  "description": "toy project",
  "main": "index.js",
  "author": "Rob Levin",
  "license": "MIT",
  "private": true,
  "workspaces": ["littlebutton-react", "littlebutton-vue", "littlebutton-svelte", "littlebutton-angular"],
  "scripts": {
    "start:react": "yarn workspace littlebutton-react dev"
  }
}

Run the yarn command from the top-level directory to get the monorepo-hoisted dependencies installed.

The only change we’ve made to this package.json is a new scripts section with a single script to start the React app. By adding start:react we can now run yarn start:react from our top-level directory and it will fire up the project we just built in ./littlebutton-react without the need for cd‘ing — super convenient!

We’ll tackle Vue and Svelte next. It turns out that we can take a pretty similar approach for these as they both use single file components (SFC). Basically, we get to mix HTML, CSS, and JavaScript all into one single file. Whether you like the SFC approach or not, it’s certainly adequate enough for building out presentational or primitive UI components.

Vue

Following the steps from vite’s scaffolding docs we’ll run the following command from the monorepo’s top-level directory to initialize a Vue app:

yarn create vite littlebutton-vue --template vue

This generates scaffolding with some provided instructions to run the starter Vue app:

cd littlebutton-vue
yarn
yarn dev

This should fire up a starter page in the browser with some heading like “Hello Vue 3 + Vite.” From here, we can update src/App.vue to:

<template>
  <div id="app">
    <Button class="btn">Go</Button>
  </div>
</template>

<script>
import Button from './components/Button.vue'

export default {
  name: 'App',
  components: {
    Button
  }
}
</script>

And we’ll replace any src/components/* with src/components/Button.vue:

<template>
  <button :class="classes"><slot /></button>
</template>

<script>
export default {
  name: 'Button',
  computed: {
    classes() {
      return {
        [this.$style.btn]: true,
      }
    }
  }
}
</script>

<style module>
.btn {
  color: slateblue;
}
</style>

Let’s break this down a bit:

  • :class="classes" is using Vue’s binding to call the computed classes method.
  • The classes method, in turn, is utilizing CSS Modules in Vue with the this.$style.btn syntax which will use styles contained in a <style module> tag.

For now, we’re hardcoding color: slateblue simply to test that things are working properly within the component. Try firing up the app again with yarn dev. If you see the button with our declared test color, then it’s working!

Now we’re going to write a Node script that copies our littlebutton-css/css/button.css into our Button.vue file similar to the one we did for the React implementation. As mentioned, this component is a SFC so we’re going to have to do this a little differently using a simple regular expression.

Add the following little Node.js script to littlebutton-vue/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
const vue = fs.readFileSync("./src/components/Button.vue", "utf8");
// Take everything between the starting and closing style tag and replace
const styleRegex = /<style module>([\s\S]*?)<\/style>/;
let withSynchronizedStyles = vue.replace(styleRegex, `<style module>\n${css}\n</style>`);
fs.writeFileSync("./src/components/Button.vue", withSynchronizedStyles, "utf8");

There’s a bit more complexity in this script, but using replace to copy text between opening and closing style tags via regex isn’t too bad.

Now let’s add the following two scripts to the scripts clause in the littlebutton-vue/package.json file:

"syncStyles": "node copystyles.js",
"dev": "yarn syncStyles && vite",

Now run yarn syncStyles and look at ./src/components/Button.vue again. You should see that our style module gets replaced with this:

<style module>
.btn {
  color: hotpink;
}
</style>

Run the Vue app again with yarn dev and verify you get the expected results — yes, a button with hotpink text. If so, we’re good to move on to the next framework workspace!

Svelte

Per the Svelte docs, we should kick off our littlebutton-svelte workspace with the following, starting from the monorepo’s top-level directory:

npx degit sveltejs/template littlebutton-svelte
cd littlebutton-svelte
yarn && yarn dev

Confirm you can hit the “Hello World” start page at http://localhost:5000. Then, update littlebutton-svelte/src/App.svelte:

<script>
  import Button from './Button.svelte';
</script>
<main>
  <Button>Go</Button>
</main>

Also, in littlebutton-svelte/src/main.js, we want to remove the name prop so it looks like this:

import App from './App.svelte';

const app = new App({
  target: document.body
});

export default app;

And finally, add littlebutton-svelte/src/Button.svelte with the following:

<button class="btn">
  <slot></slot>
</button>

<script>
</script>

<style>
  .btn {
    color: saddlebrown;
  }
</style>

One last thing: Svelte appears to name our app: "name": "svelte-app" in the package.json. Change that to "name": "littlebutton-svelte" so it’s consistent with the workspaces name in our top-level package.json file.

Once again, we can copy our baseline littlebutton-css/css/button.css into our Button.svelte. As mentioned, this component is a SFC, so we’re going to have to do this using a regular expression. Add the following Node script to littlebutton-svelte/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
const svelte = fs.readFileSync("./src/Button.svelte", "utf8");
const styleRegex = /<style>([\s\S]*?)<\/style>/;
let withSynchronizedStyles = svelte.replace(styleRegex, `<style>\n${css}\n</style>`);
fs.writeFileSync("./src/Button.svelte", withSynchronizedStyles, "utf8");

This is super similar to the copy script we used with Vue, isn’t it? We’ll add similar scripts to our package.json script:

"dev": "yarn syncStyles && rollup -c -w",
"syncStyles": "node copystyles.js",

Now run yarn syncStyles && yarn dev. If all is good, we once again should see a button with hotpink text.

If this is starting to feel repetitive, all I have to say is welcome to my world. What I’m showing you here is essentially the same process I’ve been using to build my AgnosticUI project!

Angular

You probably know the drill by now. From the monorepo’s top-level directory, install Angular and create an Angular app. If we were creating a full-blown UI library we’d likely use ng generate library or even nx. But to keep things as straightforward as possible we’ll set up a boilerplate Angular app as follows:

npm install -g @angular/cli ### unless you already have installed
ng new littlebutton-angular ### choose no for routing and CSS
? Would you like to add Angular routing? (y/N) N
❯ CSS 
  SCSS   [ https://sass-lang.com/documentation/syntax#scss ] 
  Sass   [ https://sass-lang.com/documentation/syntax#the-indented-syntax ] 
  Less   [ http://lesscss.org ]

cd littlebutton-angular && ng serve --open

With the Angular setup confirmed, let’s update some files. cd littlebutton-angular, delete the src/app/app.component.spec.ts file, and add a button component in src/components/button.component.ts, like this:

import { Component } from '@angular/core';

@Component({
  selector: 'little-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
})
export class ButtonComponent {}

Add the following to src/components/button.component.html:

<button class="btn">Go</button>

And put this in the src/components/button.component.css file for testing:

.btn {
  color: fuchsia;
}

In src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { ButtonComponent } from '../components/button.component';

@NgModule({
  declarations: [AppComponent, ButtonComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Next, replace src/app/app.component.ts with:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {}

Then, replace src/app/app.component.html with:

<main>
  <little-button>Go</little-button>
</main>

With that, let’s run yarn start and verify our button with fuchsia text renders as expected.

Again, we want to copy over the CSS from our baseline workspace. We can do that by adding this to littlebutton-angular/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
fs.writeFileSync("./src/components/button.component.css", css, "utf8");

Angular is nice in that it uses ViewEncapsulation that defaults to to emulate which mimics, according to the docs,

[…] the behavior of shadow DOM by preprocessing (and renaming) the CSS code to effectively scope the CSS to the component’s view.

This basically means we can literally copy over button.css and use it as-is.

Finally, update the package.json file by adding these two lines in the scripts section:

"start": "yarn syncStyles && ng serve",
"syncStyles": "node copystyles.js",

With that, we can now run yarn start once more and verify our button text color (which was fuchsia) is now hotpink.

What have we just done?

Let’s take a break from coding and think about the bigger picture and what we’ve just done. Basically, we’ve set up a system where any changes to our CSS package’s button.css will get copied over into all the framework implementations as a result of our copystyles.js Node scripts. Further, we’ve incorporated idiomatic conventions for each of the frameworks:

  • SFC for Vue and Svelte
  • CSS Modules for React (and Vue within the SFC <style module> setup)
  • ViewEncapsulation for Angular

Of course I state the obvious that these aren’t the only ways to do CSS in each of the above frameworks (e.g. CSS-in-JS is a popular choice), but they are certainly accepted practices and are working quite well for our greater goal — to have a single CSS source of truth to drive all framework implementations.

If, for example, our button was in use and our design team decided we wanted to change from 4px to 3px border-radius, we could update the one file, and any separate implementations would stay synced.

This is compelling if you have a polyglot team of developers that enjoy working in multiple frameworks, or, say an offshore team (that’s 3× productive in Angular) that’s being tasked to build a back-office application, but your flagship product is built in React. Or, you’re building an interim admin console and you’d love to experiment with using Vue or Svelte. You get the picture.

Finishing touches

OK, so we have the monorepo architecture in a really good spot. But there’s a few things we can do to make it even more useful as far as the developer experience goes.

Better start scripts

Let’s move back up to our top-level monorepo directory and update its package.json scripts section with the following so we can kick any framework implementation without cd‘ing:

// ...
"scripts": {
  "start:react": "yarn workspace littlebutton-react dev",
  "start:vue": "yarn workspace littlebutton-vue dev ",
  "start:svelte": "yarn workspace littlebutton-svelte dev",
  "start:angular": "yarn workspace littlebutton-angular start"
},

Better baseline styles

We can also provide a better set of baseline styles for the button so it starts from a nice, neutral place. Here’s what I did in the littlebutton-css/css/button.css file.

View Full Snippet
.btn {
  --button-dark: #333;
  --button-line-height: 1.25rem;
  --button-font-size: 1rem;
  --button-light: #e9e9e9;
  --button-transition-duration: 200ms;
  --button-font-stack:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    Ubuntu,
    "Helvetica Neue",
    sans-serif;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  user-select: none;
  appearance: none;
  cursor: pointer;
  box-sizing: border-box;
  transition-property: all;
  transition-duration: var(--button-transition-duration);
  color: var(--button-dark);
  background-color: var(--button-light);
  border-color: var(--button-light);
  border-style: solid;
  border-width: 1px;
  font-family: var(--button-font-stack);
  font-weight: 400;
  font-size: var(--button-font-size);
  line-height: var(--button-line-height);
  padding-block-start: 0.5rem;
  padding-block-end: 0.5rem;
  padding-inline-start: 0.75rem;
  padding-inline-end: 0.75rem;
  text-decoration: none;
  text-align: center;
}

/* Respect users reduced motion preferences */
@media (prefers-reduced-motion) {
  .btn {
    transition-duration: 0.001ms !important;
  }
}

Let’s test this out! Fire up each of the four framework implementations with the new and improved start scripts and confirm the styling changes are in effect.

Neutral (gray) styled button from the monorepo framework

One CSS file update proliferated to four frameworks — pretty cool, eh!?

Set a primary mode

We’re going to add a mode prop to each of our button’s and implement primary mode next. A primary button could be any color but we’ll go with a shade of green for the background and white text. Again, in the baseline stylesheet:

.btn {
  --button-primary: #14775d;
  --button-primary-color: #fff;
  /* ... */
}

Then, just before the @media (prefers-reduced-motion) query, add the following btn-primary to the same baseline stylesheet:

.btn-primary {
  background-color: var(--button-primary);
  border-color: var(--button-primary);
  color: var(--button-primary-color);
}

There we go! Some developer conveniences and better baseline styles!

Updating each component to take a mode property

Now that we’ve added our new primary mode represented by the .btn-primary class, we want to sync the styles for all four framework implementations. So, let’s add some more package.json scripts to our top level scripts:

"sync:react": "yarn workspace littlebutton-react syncStyles",
"sync:vue": "yarn workspace littlebutton-vue syncStyles",
"sync:svelte": "yarn workspace littlebutton-svelte syncStyles",
"sync:angular": "yarn workspace littlebutton-angular syncStyles"

Be sure to respect JSON’s comma rules! Depending on where you place these lines within your scripts: {...}, you’ll want to make sure there are no missing or trailing commas.

Go ahead and run the following to fully synchronize the styles:

yarn sync:angular && yarn sync:react && yarn sync:vue && yarn sync:svelte

Running this doesn’t change anything because we haven’t applied the primary class yet, but you should at least see the CSS has been copied over if you go look at the framework’s button component CSS.

React

If you haven’t already, double-check that the updated CSS got copied over into littlebutton-react/src/button.css. If not, you can run yarn syncStyles. Note that if you forget to run yarn syncStyles our dev script will do this for us when we next start the application anyway:

"dev": "yarn syncStyles && vite",

For our React implementation, we additionally need to add a composed CSS Modules class in littlebutton-react/src/button.module.css that is composed from the new .btn-primary:

.btnPrimary {
  composes: btn-primary from './button.css';
}

We’ll also update littlebutton-react/src/App.jsx:

import "./App.css";
import styles from "./button.module.css";

const Button = ({ mode }) => {
  const primaryClass = mode ? styles[`btn${mode.charAt(0).toUpperCase()}${mode.slice(1)}`] : '';
  const classes = primaryClass ? `${styles.btn} ${primaryClass}` : styles.btn;
  return <button className={classes}>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button mode="primary" />
    </div>
  );
}

export default App;

Fire up the React app with yarn start:react from the top-level directory. If all goes well, you should now see your green primary button.

A dark green button with white text positioning in the center of the screen.

As a note, I’m keeping the Button component in App.jsx for brevity. Feel free to tease out the Button component into its own file if that bothers you.

Vue

Again, double-check that the button styles were copied over and, if not, run yarn syncStyles.

Next, make the following changes to the <script> section of littlebutton-vue/src/components/Button.vue:

<script>
export default {
  name: 'Button',
  props: {
    mode: {
      type: String,
      required: false,
      default: '',
      validator: (value) => {
        const isValid = ['primary'].includes(value);
        if (!isValid) {
          console.warn(`Allowed types for Button are primary`);
        }
        return isValid;
      },
    }
  },
  computed: {
    classes() {
      return {
        [this.$style.btn]: true,
        [this.$style['btn-primary']]: this.mode === 'primary',
      }
    }
  }
}
</script>

Now we can update the markup in littlebutton-vue/src/App.vue to use the new mode prop:

<Button mode="primary">Go</Button>

Now you can yarn start:vue from the top-level directory and check for the same green button.

Svelte

Let’s cd into littlebutton-svelte and verify that the styles in littlebutton-svelte/src/Button.svelte have the new .btn-primary class copied over, and yarn syncStyles if you need to. Again, the dev script will do that for us anyway on the next startup if you happen to forget.

Next, update the Svelte template to pass the mode of primary. In src/App.svelte:

<script>
  import Button from './Button.svelte';
</script>
<main>
  <Button mode="primary">Go</Button>
</main>

We also need to update the top of our src/Button.svelte component itself to accept the mode prop and apply the CSS Modules class:

<button class="{classes}">
  <slot></slot>
</button>
<script>
  export let mode = "";
  const classes = [
    "btn",
    mode ? `btn-${mode}` : "",
  ].filter(cls => cls.length).join(" ");
</script>

Note that the <styles> section of our Svelte component shouldn’t be touched in this step.

And now, you can yarn dev from littlebutton-svelte (or yarn start:svelte from a higher directory) to confirm the green button made it!

Angular

Same thing, different framework: check that the styles are copied over and run yarn syncStyles if needed.

Let’s add the mode prop to the littlebutton-angular/src/app/app.component.html file:

<main>
  <little-button mode="primary">Go</little-button>
</main>

Now we need to set up a binding to a classes getter to compute the correct classes based on if the mode was passed in to the component or not. Add this to littlebutton-angular/src/components/button.component.html (and note the binding is happening with the square brackets):

<button [class]="classes">Go</button>

Next, we actually need to create the classes binding in our component at littlebutton-angular/src/components/button.component.ts:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'little-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
})
export class ButtonComponent {
  @Input() mode: 'primary' | undefined = undefined;

  public get classes(): string {
    const modeClass = this.mode ? `btn-${this.mode}` : '';
    return [
      'btn',
      modeClass,
    ].filter(cl => cl.length).join(' ');
  }
}

We use the Input directive to take in the mode prop, then we create a classes accessor which adds the mode class if it’s been passed in.

Fire it up and look for the green button!

Code complete

If you’ve made it this far, congratulations — you’ve reached code complete! If something went awry, I’d encourage you to cross-reference the source code over at GitHub on the the-little-button-that-could-series branch. As bundlers and packages have a tendency to change abruptly, you might want to pin your package versions to the ones in this branch if you happen to experience any dependency issues.

Take a moment to go back and compare the four framework-based button component implementations we just built. They’re still small enough to quickly notice some interesting differences in how props get passed in, how we bind to props, and how CSS name collisions are prevented among other subtle differences. As I continue to add components to AgnosticUI (which supports these exact same four frameworks), I’m continually pondering which offers the best developer experience. What do you think?

Homework

If you’re the type that likes to figure things out on your own or enjoys digging in deeper, here are ideas.

Button states

The current button styles do not account for various states, like :hover. I believe that’s a good first exercise.

/* You should really implement the following states
   but I will leave it as an exercise for you to 
   decide how to and what values to use.
*/
.btn:focus {
  /* If you elect to remove the outline, replace it
     with another proper affordance and research how
     to use transparent outlines to support windows
     high contrast
  */
}
.btn:hover { }
.btn:visited { }
.btn:active { }
.btn:disabled { }

Variants

Most button libraries support many button variations for things like sizes, shapes, and colors. Try creating more than the primary mode we already have. Maybe a secondary variation? A warning or success? Maybe filled and outline? Again, you can look at AgnosticUI’s buttons page for ideas.

CSS custom properties

If you haven’t started using CSS custom properties yet, I’d strongly recommend it. You can start by having a look at AgnosticUI’s common styles. I heavily lean on custom properties in there. Here are some great articles that cover what custom properties are and how you might leverage them:

Types

No… not typings, but the <button> element’s type attribute. We didn’t cover that in our component but there’s an opportunity to extend the component to other use cases with valid types, like button, submit, and reset. This is pretty easy to do and will greatly improve the button’s API.

More ideas

Gosh, you could do so much — add linting, convert it to Typescript, audit the accessibility, etc.

The current Svelte implementation is suffering from some pretty loose assumptions as we have no defense if the valid primary mode isn’t passed — that would produce a garbage CSS class:

mode ? `btn-${mode}` : "",

You could say, “Well, .btn-garbage as a class isn’t exactly harmful.” But it’s probably a good idea to style defensively when and where possible.

Potential pitfalls

There are some things you should be aware of before taking this approach further:

  • Positional CSS based on the structure of the markup will not work well for the CSS Modules based techniques used here.
  • Angular makes positional techniques even harder as it generates :host element representing each component view. This means you have these extra elements in between your template or markup structure. You’ll need to work around that.
  • Copying styles across workspace packages is a bit of an anti-pattern to some folks. I justify it because I believe the benefits outweigh the costs; also, when I think about how monorepos use symlinks and (not-so-failproof) hoisting, I don’t feel so bad about this approach.
  • You’ll have to subscribe to the decoupled techniques used here, so no CSS-in-JS.

I believe that all approaches to software development have their pros and cons and you ultimately have to decide if sharing a single CSS file across frameworks works for you or your specific project. There are certainly other ways you could do this (e.g. using littlebuttons-css as an npm package dependency) if needed.

Conclusion

Hopefully I’ve whet your appetite and you’re now really intrigued to create UI component libraries and/or design systems that are not tied to a particular framework. Maybe you have a better idea on how to achieve this — I’d love to hear your thoughts in the comments!

I’m sure you’ve seen the venerable TodoMVC project and how many framework implementations have been created for it. Similarly, wouldn’t it be nice to have a UI component library of primitives available for many frameworks? Open UI is making great strides to properly standardize native UI component defaults, but I believe we’ll always need to insert ourselves to some extent. Certainly, taking a good year to build a custom design system is quickly falling out of favor and companies are seriously questioning their ROI. Some sort of scaffolding is required to make the endeavor practical.

The vision of AgnosticUI is to have a relatively agnostic way to build design systems quickly that are not tied down to a particular frontend framework. If you’re compelled to get involved, the project is still very early and approachable and I’d love some help! Plus, you’re already pretty familiar with the how the project works now that you’ve gone through this tutorial!


How to Make a Component That Supports Multiple Frameworks in a Monorepo originally published on CSS-Tricks. You should get the newsletter and become a supporter.

System on Modules (SOM) and Its End-to-End Verification Using Test Automation Framework

SOM is an entire CPU architecture built in a small package, of size like a credit card. It is a board-level circuit that integrates a system function and provides core components of an embedded processing system - processor cores, communication interfaces, and memory blocks on a single module. Designing any product based on the SOM is a much faster process than designing the entire system from the ground up. 

There are multiple System on Module manufacturers available in the market worldwide with an equal amount of open-source automated testing frameworks. If you plan to use System-on-Module (SOM) in your product, the first thing required is to identify the test automation framework from the ones available out in the market and check for a suitable module for your requirement. 

Tips for Cutting Down Web Development Time

When planning the creation of a website or web app, underestimating the time needed to deliver can sink a project. The longer the project is in development the more money, time, and other resources are required to see it to delivery. Also, the longer it is developed, the more that excitement for the project wanes and it might just be cast to the rubbish heap of other discarded projects.

Fortunately, over the years with advancements in tech stacks and the development of best practices, development times can be significantly reduced. This article looks at 10 easy-to-adopt practices that will reduce development time.

The Process of Building a CSS Framework

We recently released version 3 of CodyFrame, a CSS framework Sebastiano (my partner at CodyHouse) and I have been working on for the last three years.

In this article, I want to show you the steps that brought us to this last version, the key features of the framework, and how CodyFrame and the library of components that come with it – almost 400 – can be used to create web pages in no time.

Here’s a preview of what we’ll build:

Live demo:

https://ambercreative.co/codrops/

Click here to skip the ‘framework story’ and go straight to the tutorial.

Why we built CodyFrame and how it evolved

7 years ago, we started CodyHouse as a collection of web design experiments. We did so to promote our web design agency. There weren’t many websites back then that were sharing similar content (Codrops was one of our main inspirations).

Our library grew a lot in popularity, but we soon realised how difficult it was to include multiple experiments in the same project: they had disconnected CSS code, which required extracting the common style (e.g., buttons, typography) to avoid code repetition and style inconsistency.

This is why we decided to build CodyFrame and a new library of connected components.

The original idea was to create a super light CSS framework that would include only a set of global styles (e.g., colors, form elements, and buttons) that we could reuse each time a new component was created.

As the library kept growing, we started facing a few problems: by including layout decisions into the components (e.g., margins and paddings), it was difficult to create layout variations.

Our components are built using the BEM naming convention, so even the smallest change required a component variation:

<div class="component component--padding-y-sm">
  <!-- ... -->
</div>

<div class="component component--margin-y-xl">
  <!-- ... -->
</div>

<style>
  .component {
    padding: var(--space-md) 0;
    margin: var(--space-md) 0;
  }

  .component--padding-y-sm {
    padding: var(--space-sm) 0;
  }

  .component--margin-y-xl {
    margin: var(--space-xl) 0;
  }
</style>

We decided to include a few utility classes into the framework, and it turned out to be super useful not only to dry the components code but also to make them easier to customise.

<div class="component margin-y-md padding-y-sm">
  <!-- ... -->
</div>

<div class="component margin-y-xl padding-y-md">
  <!-- ... -->
</div>

We’ve since then included a lot of utility classes and developed a system to build a scalable CSS architecture based on BEM and utility classes.

CodyFrame v2

In v1, each global file contained both the base rules and the custom style:

.btn {
	/* base style */
	display: inline-flex;
	text-decoration: none;

	/* custom style */  
	background-color: var(--color-primary);
	color: var(--color-white);
}

One flaw of this version was the upgrade process: when a new version of the framework was released, the changes to the base code would be mixed up with the custom code the user had already modified.

To fix this issue, in CodyFrame v2 we decided to split the framework into two parts:

  1. Basic style – essential CSS rules and utility classes;
  2. Custom style – SCSS templates to create your bespoke style.
css/
  ├── base/
  │   ├── _accessibility.scss
  │   ├── _buttons.scss
  │   ├── _colors.scss
  │   ├── _forms.scss
  │   ├── _icons.scss
  │   ├── _spacing.scss
  │   ├── _typography.scss
  │   └── _util.scss
  └── custom-style/
      ├── _buttons.scss
      ├── _colors.scss
      ├── _forms.scss
      ├── _icons.scss
      ├── _spacing.scss
      └── _typography.scss

Each time a new version of the framework is released, our users can replace the ‘base’ folder with the new one without affecting their bespoke style!

CSS Custom Properties

Since the beginning, we have been using CSS custom properties to handle variables in CodyFrame.

CSS variables are great for code maintenance: you define the variable in one place and use it everywhere in your code. If you need to modify its value later, you only need to update one line of code, and it will propagate through the entire codebase.

But we picked them (mostly) for a different reason: unlike SASS variables, you can update the value of a CSS variable using a class or media queries.

.icon {
  --size: 1em;
  height: var(--size);
  width: var(--size);
}

.icon--sm {
  --size: 16px;
}

This difference opens a world of possibilities. We’ve built a system where spacing and typography are controlled by a few CSS variables, with almost no need for media queries.

Why not using an existing framework

Building a framework is a learning process. It taught us a lot about code maintainability, and it freed us from chasing other frameworks updates.

Sure, it takes time to convince developers we’re worth a try. But, in the long term, it’s a win-win deal for both us and our members.

CodyFrame v2 turned out to be very stable: it has been out for more than 2 years, with more than 40 minor releases.

CodyFrame v3 was launched a few days ago to include the new SASS Modules with some important tweaks to the color and spacing systems.

Our framework is now downloaded almost 10’000 times/month, and I consider this a great achievement.

Plans for the future

We just released CodyFrame v3, and we are still keeping an eye out for issue reports.

We plan to keep shipping new components and templates until we run out of ideas. We’re also looking into native web components, that would make our components compatible with all the popular JS frameworks.

CodyFrame in action

Now the fun part! Let’s build something with CodyFrame and the components!

Here’s the final result:

https://ambercreative.co/codrops/

To keep the tutorial short and sweet, we’ll include CodyFrame via CDN:

<!doctype html>
<html lang="en">
  <head>
    <script>document.getElementsByTagName("html")[0].className += " js";</script>
    <link rel="stylesheet" href="https://unpkg.com/codyhouse-framework/main/assets/css/style.min.css">
    <link rel="stylesheet" href="assets/style.css">
  </head>
  <body>
    <!-- ... -->

    <script src="https://unpkg.com/codyhouse-framework/main/assets/js/util.js"></script>
    <script src="assets/script.js"></script>
  </body>
</html>

If you want to create your custom style (e.g., modify buttons, typography, …), you can download the SCSS templates included in the framework and modify them using our global editors, as explained in our Documentation.

As the main navigation of the page, we’ll use the Main Header components from our UI library.

Copy the HTML, CSS and JS code using the buttons in the top-right corner of the page and include them in your project.

We can customise the HTML structure of this header: let’s delete the code for the ‘Download’ button item and the divider (.header__item--divider) and let’s remove the aria-current="page" attribute from the ‘Resources’ link – that attribute is used to style the current page link. In our case, it is the home page, so we won’t need to set it for any of the header links:

<header class="header position-relative js-header">
  <div class="header__container container max-width-lg">
    <div class="header__logo">
      <a href="#0">
        <svg width="104" height="30" viewBox="0 0 104 30">
          <title>Go to homepage</title>
          <!-- ... -->
        </svg>
      </a>
    </div>

    <button class="btn btn--subtle header__trigger js-header__trigger" aria-label="Toggle menu" aria-expanded="false" aria-controls="header-nav">
      <i class="header__trigger-icon" aria-hidden="true"></i>
      <span>Menu</span>
    </button>

    <nav class="header__nav js-header__nav" id="header-nav" role="navigation" aria-label="Main">
      <div class="header__nav-inner">
        <div class="header__label">Main menu</div>
        <ul class="header__list">
          <li class="header__item">
            <a href="#0" class="header__link">About</a>
          </li>
          <!-- more list items here -->
        </ul>
      </div>
    </nav>
  </div>
</header>

For more info on the Main Header component, you can check its Info page.

For the hero section, we’ll be using some of the CodyFrame utility classes.

<section class="container max-width-adaptive-md padding-top-xl padding-bottom-xxl">
  <h1>Lorem ipsum dolor sit amet consectetur adipisicing elit</h1>

  <p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>

  <div class="flex items-center gap-sm">
    <a href="#0" class="btn btn--primary">Download</a>
    <a href="#0" class="color-inherit">Learn more</a>
  </div>
</section>

The .container and .max-width-adaptive-md classes are used to set the width of the hero element. The flex classes (.flex/.items-center/.gap-sm) are used to align the action buttons and create a gap.

To complete the hero section, we’ll need to 1) animate the headline with rotating words, 2) create the offset effect of the paragraph element, and 3) create the hover effect for the ‘Learn more’ link.

Hero section preview

For the offset effect, we can use the grid utility classes: 1) the .col class (and its responsive modifier .col-8@md) to change the element width at a specific breakpoint, and 2) the .offset class to offset it.

<div class="grid">
  <h1>Lorem ipsum dolor sit amet consectetur adipisicing elit</h1>

  <div class="col-8@md offset-4@md">
    <p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>

    <div class="flex items-center gap-sm">
      <a href="#0" class="btn btn--primary">Download</a>
      <a href="#0" class="color-inherit">Learn more</a>
    </div>
  </div>
</div>

To animate the headline words, we can use the Animated Headline component (the ‘clip’ variation).

Let’s copy the CSS and JS code of the component in our project. Then use the HTML to replace the <h1> element of our hero section:

<div class="grid">
  <h1 class="text-xxxl text-anim text-anim--clip js-text-anim">
    We <span class="text-anim__wrapper js-text-anim__wrapper"><i class="text-anim__word text-anim__word--in js-text-anim__word">design</i><i class="text-anim__word js-text-anim__word">develop</i><i class="text-anim__word js-text-anim__word">create</i></span> digital experiences
  </h1>

  <div class="col-8@md offset-4@md">
    <p class="line-height-md margin-y-md">Lorem ipsum dolor ...</p>

    <div class="flex items-center gap-sm">
      <a href="#0" class="btn btn--primary">Download</a>
      <a href="#0" class="color-inherit">Learn more</a>
    </div>
  </div>
</div>

Finally, for the link hover effect, let’s use the Link Effects component. Import the CSS code, then copy the HTML and use it to replace the original <a> element:

<a class="link-fx-1 color-contrast-higher" href="#0">
  <span>Learn more</span>
  <svg class="icon" viewBox="0 0 32 32" aria-hidden="true">
    <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><!-- ... --></g>
  </svg>
</a>

For the next section, let’s use the Sticky Hero component – the ‘scale’ variation – with some minor customisations.

Sticky Hero preview

First, we’ll copy the HTML, CSS and JS code of the component in our project.

In the HTML, we can update the content of the .sticky-hero__content to include a <blockquote> element:

<div class="sticky-hero__content">
  <blockquote class="container max-width-sm text-center color-white">
    <p class="text-xxl">One belongs to New York instantly, one belongs to it as much in five minutes as in five years.</p>

    <footer class="margin-top-lg">— Tom Wolfe</footer>
  </blockquote>
</div>

Now let’s create the gallery section. The HTML will include a heading element and images:

<div class="container max-width-adaptive-lg padding-y-xl">
  <div>
    <h2 class="text-md font-medium border-top padding-top-xs">Selected Works</h2>

    <figure>
      <img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
    </figure>

    <figure>
      <img src="assets/img/gallery-img-2.jpg" alt="Image Preview">
    </figure>

    <figure>
      <img src="assets/img/gallery-img-3.jpg" alt="Image Preview">
    </figure>

    <figure>
      <img src="assets/img/gallery-img-4.jpg" alt="Image Preview">
    </figure>
  </div>
</div>

On mobile, we want the elements to be one below the other, while on a bigger screen, they can be positioned to create a grid. Again, we’ll make use of the grid utility classes:

<div class="container max-width-adaptive-lg padding-y-xl">
  <div class="grid gap-xs">
    <h2 class="col-4@md text-md font-medium border-top padding-top-xs">Selected Works</h2>

    <figure class="col-8@md">
      <img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
    </figure>

    <figure class="col-8@md">
      <img src="assets/img/gallery-img-2.jpg" alt="Image Preview">
    </figure>

    <figure class="col-4@md">
      <img src="assets/img/gallery-img-3.jpg" alt="Image Preview">
    </figure>

    <figure class="offset-1@md col-10@md">
      <img src="assets/img/gallery-img-4.jpg" alt="Image Preview">
    </figure>
  </div>
</div>

You can play with the .col-{number} and .offset-{number} classes to create a different grid layout!

To animate the gallery images as they enter the viewport, let’s use the Reveal Effects component.

Of the different animations, we’ll use the rotate-up. Make sure to include the CSS and JS of the component, then add the .reveal-fx.reveal-fx--rotate-up classes, as explained in the Info page:

<figure class="col-8@md reveal-fx reveal-fx--rotate-up">
  <img src="assets/img/gallery-img-1.jpg" alt="Image Preview">
</figure>
Image gallery preview

For the next section, we are going to create a single-column layout on mobile, and a two-columns on bigger screens:

<div class="bg-dark padding-y-xl">
  <div class="container max-width-adaptive-lg">
    <div class="grid gap-md">
      <div class="col-4@md">
        <!-- content of first column -->
      </div>
      
      <div class="col-8@md">
        <!-- content of second column -->
      </div>
    </div>
  </div>
</div>

Now we can add a heading element to the first column and some text elements to the second column.

<div class="col-4@md">
  <h3 class="text-xxl">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</h3>
</div>

<div class="col-8@md">
  <div class="text-component">
    <p>Lorem ipsum dolor sit amet...</p>

    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>

    <a href="#0">Read more</a>
  </div>
</div>

The .text-component class is used to stylise blocks containing typography, taking care of vertical rhythm and styling inline elements.

To complete this section, let’s add a hover effect to the link at the bottom of the second column, as we did for the link in the top hero section! The CSS of the Link Effects component has already been included, we only need to copy the new HTML and replace the original link:

<div class="col-8@md">
  <div class="text-component">
    <p>Lorem ipsum dolor sit amet...</p>

    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit...</p>

    <a class="link-fx-1 color-contrast-higher" href="#0">
      <span>Read more</span>
      <svg class="icon" viewBox="0 0 32 32" aria-hidden="true">
        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><!-- ... --></g>
      </svg>
    </a>
  </div>
</div>

Our page is almost complete! We still have two sections + the footer 💪
For those sections, we can use three components from the UI library:

  1. The Stacking Cards component – the ‘bg-images’ variation;
  2. The Newsletter component;
  3. The Main Footer component.

And here’s the complete page:

https://ambercreative.co/codrops/

CodyFrame comes with a built-in dark theme! To activate dark more, add the data-theme="dark" to the body element, no additional changes are required!

<body data-theme="dark">
  <!-- page content here -->
</body>

Here’s a preview of the same page in dark mode:

https://ambercreative.co/codrops/dark-theme.html

You can get the complete code on GitHub.

Hope you enjoyed this article! We’re always open to feedback and suggestions! Share your thoughts in the comments or message us on Twitter.

The post The Process of Building a CSS Framework appeared first on Codrops.

Building a Metadata Driven UI

Description

Metadata-driven UI is especially useful in project teams with a high back-end or DBA competence rather than UI.

In general, it provides an element alignment by invocation of a single endpoint which provides all data required like cardinality, language, font size, and the font itself.

Tonic (Component Framework)

I enjoy little frameworks like Tonic. It’s essentially syntactic sugar over <web-components /> to make them feel easier to use. Define a Class, template literal an HTML template, probably some other fancy helpers, and you’ve got a component that doesn’t feel terribly different to something like a React component, except you need no build process or other exotic tooling.

Here’s a Hello World + Counter example:

They have a whole bunch of examples (in a separate repo). You can snag and use them, and they are pretty nice! So that makes Tonic a bit like a design system as well as a web component framework.

To be fair, it’s not that different from Lit, which Google is behind and pushing pretty actively.

Here’s a Hello, World + Counter with Lit:

And Dave was just showing me petite-vue the other day, so I figured I might as well do that one, too:

I’d say that petite-vue example wins for just how super easy that is to pull of in just declarative HTML. But of course, there are a bunch of other considerations from specific features, syntax, philosophy, and size. Just looking at size, if I pop open the Network tab in DevTools and see the over-the-wire JavaScript for each demo…

  • Tonic = 5.1 KB
  • Lit = 12.6 KB
  • petite-vue = 8.1 KB

They are all basically the same: tiny.

I’ve never actually built anything real in any of them, so I’m not the best to judge one from the other. But they all seem pretty neat to me, particularly because they require no build step.


The post Tonic (Component Framework) appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

A Spring Boot Developer’s Guide To Micronaut

This is a guide for Spring application developers who want to get started with using Micronaut. With this guide, you will get enough information to work with the Micronaut framework.

Introduction

Micronaut is a framework, which has gained its name for faster startup time and is usually preferred for solutions with AWS Lambda. It uses Ahead Of Time (AOT) Compilation to determine what the application needs. The result of this is an application that has a smaller memory footprint, fast start-up time, and is reflection-free.

Is Blitz.js the JavaScript Framework I’ve Been Waiting For?

The year was 2007. I was about to leave corporate America and try my first startup. I had been a Java architect for the past 10+ years and was ready to try something new. A friend of mine introduced me to a framework called “Ruby on Rails.” She said it was amazing how fast you could build stuff. Here is a link to the original “How to build a blog engine in 15 minutes with Ruby on Rails” video where David Heinemeier Hansson (DHH) shows us how to build a blog using Rails. After watching the video, I was sold that it was worth a try. I spent most of the next year building my startup primarily programming in Ruby on Rails. It took me a while to climb the fairly steep learning curve of Rails, and also to really learn the Ruby programming language, but once I did, I could build stuff faster than at any point in my 20+ year career. Rails has a doctrine of what makes it great; you can look at the nine items here. Looking back, what made Rails great for me personally was a handful of key things:

  • Convention over Configuration – Now most frameworks follow this paradigm, but Rails was the first to bring it to the masses. No more struggling with configuration files to get your environment set up properly, everything works right out of the box, and if you follow the rules, it’s painless.
  • DRY Coding – (Do NOT repeat yourself) – The idea here is that you only need to code something in one place to get it done. You don’t need to copy and paste code into five different places to get something to work. 
  • Developer Happiness & Productivity – Rails came with generators to get you started. They would create all the code you need to CRUD (Create, Read, Update, Delete) your resources. Rails also came with a handy console where you could test and debug your code in a full working environment. You could also code everything in one language, Rails provided helpers to generate most of the JavaScript you would need. Rails eliminated the multiple layers upon layers of coding you used to have to do to get something done.
  • True Reuse – We have been looking for the ability to reuse code for 20+ years. We have tried many different approaches, but Rails, because it is a complete ecosystem, has done the best job at it. When you are building an app, you can find a “gem” (which is a library) to do just about anything you want. For example, authentication, authorization, uploading files, paginating results – all these things come for free by using “gems” that are open source.
  • Majestic Monolith – For most of the applications we build, having a fully integrated, simple, coherent platform, and structure is the easiest way to build things. Sure, microservices and serverless have their place, but for 80% of the applications out there, having everything set up in one platform is the easiest way to construct software. Rails comes with all you need to build your User Interface, API, business logic and database.

Also worth noting is that Enterprise Java was the “state of the art” application architecture before Rails. The architecture of Enterprise Java looked something like this:

Retail Data Framework — An Architectural Introduction

article imageThis article launches a new series exploring a retail architecture blueprint. It's focusing on presenting access to ways of mapping successful implementations for specific use cases.

It's an interesting challenge creating architectural content based on common customer adoption patterns. That's very different from most of the traditional marketing activities usually associated with generating content for the sole purpose of positioning products for solutions. When you're basing the content on actual execution in solution delivery, you're cutting out the chuff.

Top Microservices Frameworks

Microservices architecture is a methodology wherein fragment monolithic single application into small applications and services which executes lightweight applications. Business capabilities and independently deployable models are the primary goals for Microservices development. Microservices architecture built using different programming languages and deployed them and connect.

Benefits of Microservices

  • Adoption of New technology and process.
  • Independent scaling of applications.
  • Cloud-ready.
  • Seamless integrations.
  • Effective Hardware utilization.
  • Service level Security.
  • API-based functions for reuse effectively.
  • Independently Develop and Deploy applications.

Selection Criteria for Framework Selection

The following are some of the critical aspects that can be considered while choosing the proper framework:

Astro

You can’t even look at code or documentation for Astro (publicly) yet — it’s an in-progress idea — but you can watch a video of Fred showing it off to Feross.

I gotta admit: it looks awesome. I’m bullish on two major parts of this:

  1. Jamstack is a good idea. Producing static, pre-rendered, minimal (or no) JavaScript pages is smart.
  2. Components are a good idea. Crafting interfaces from composable components is the correct abstraction. JavaScript does it best right now because of things like ES Modules, template literals, web components, deeply developed tooling, etc.

I’m a fan of Eleventy also, and this feels like Eleventy in a way, except that I don’t like any of the templating languages as much as I like JavaScript components.

Here’s a list of some interesting aspects:

  • Like Vue has .vue files and Svelte has .svelte files, Astro has .astro files in a unique format. I like how it enforces JavaScript-at-the-top in a Frontmatter-like format.
  • It doesn’t replace other JavaScript libraries. It’s like a site-builder framework on top of them. You can literally use React and JSX components, or Vue files, or Svelte files, including using that library’s state management solutions. You import them in your Astro files.
  • It has the-filesystem-is-the-default-router, like Next.
  • It has scoped-CSS-by-default like Vue’s <style scoped>, meaning it doesn’t even need CSS Modules since you get all the benefit anyway.
  • It ships no JavaScript to the front-end at all, unless you specifically opt-in to it (or use its :visible syntax, which injects just enough JavaScript to lazy load more as needed).
  • It embraces the idea of Islands Architecture — the idea that most sites are composed of static content with only parts of interactive/dynamic content.
  • The idea of only requesting JavaScript for interactive components if they are visible (via IntersectionObserver) is a first-class citizen of the framework — Kinda like loading="lazy" for anything interactive.
  • They credit Marko (the HTML/JavaScript-kind hybrid language) right on the homepage (for “asking the question”). Reminds me of approaches like Alpine or htmx.
  • It sneaks MDX (or the like) in there, meaning you can author content in Markdown (good) but sneak <Components /> in there too (also good).

I quite like that it doesn’t have this whole, This is a new thing! You like it! Old things are bad! New things are good! sort of vibe. Instead, it has a We’re gonna steal every last good idea we can from what came before, and lean on what the native web does best vibe which, in turn, makes me think of Baldur Bjarnason’s “Which type of novelty-seeking web developer are you?” article

Bad:

This is the first kind of novelty-seeking web developer. The type that sees history only as a litany of mistakes and that new things must be good because they are new. Why would anybody make a new thing unless it was an improvement on the status quo? Ergo, it must be an improvement on the status quo.

Good:

This is the other kind of novelty-seeking web developer, one who seeks to build on the history and nature of the web instead of trying to transform it.


The post Astro appeared first on CSS-Tricks.

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

3 New Java Tools to Try in 2021

Despite the popularity of Python, Go, and Node.js for implementing artificial intelligence and machine learning applications and serverless functions on Kubernetes, Java technologies still play a key role in developing enterprise applications. According to Developer Economics, in Q3 2020, there were 8 million enterprise Java developers worldwide.

Although the programming language has been around for more than 25 years, there are always new trends, tools, and frameworks in the Java world that can empower your applications and your career.

Web Frameworks: Why You Don’t Always Need Them

Richard MacManus explaining Daniel Kehoe’s approach to building websites, which he calls “Stackless”:

There are three key web technologies underpinning Kehoe’s approach:

  • ES6 Modules: JavaScript ES6 can support import modules, which are also supported by browsers.
  • Module CDNs: JavaScript modules can now be downloaded from third-party content delivery networks (CDNs).
  • Custom HTML elements: Developers can now create custom HTML tags, via Web Components.

Using a no build process and only features that are built into browser, and yet that still buys you a pretty powerful setup. You can still use stuff off npm. You can still get templating. You can still build with components. You still get isolation where needed.

I’d say today you’re:

  • Giving up some DX (hot module reloading, JSX, framework doodads)
  • Gaining some DX (can jump into project and just start working)
  • Giving up some performance (no tree shaking, loads of network requests)
  • Widening your hiring pool (more people know core technologies than specific tools)

But it’s not hard to imagine a tomorrow where we give up less and gain more, making the tools we use today less necessary. I’m quite sure we’ll always still find a way to jam more tools into what we’re doing. Hammer something something nail.

Direct Link to ArticlePermalink


The post Web Frameworks: Why You Don’t Always Need Them appeared first on CSS-Tricks.

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