Adam Wathan

Follow this blog

Composing the Uncomposable with CSS Variables

Many CSS properties are shorthands for a set of other properties, for example the margin property is a shorthand for setting margin-top, margin-right, margin-bottom, and margin-left all at once. Because the margin property decomposes into those four separate properties, it translates well to a utility class system like Tailwind CSS, where we can create separate utility classes for each property, then compose them arbitrarily in HTML: <style> .mt-2 { margin-top: 0.5rem; } /* ... */ .mr-6 { margin-right: 1.5rem; } /* ... */ .mb-8 { margin-bottom: 2rem; } /* ... */ .ml-4 { margin-left: 1rem; } </style> <div class="mt-2 mr-6 mb-8 ml-4"> <!-- ... --> </div> Without this composability, it would be essentially impossible to do all of the things you can do in pure CSS because of the combinatoric explosion of classes that would have to exist to support every combination of values. For example, look at this absurdity: .mt-2_mr-0_mb-0_ml-0 { margin: 0.5rem 0 0 0; } .mt-2_mr-0_mb-0_ml-1 { margin: 0.5rem 0 0 0.25rem; } .mt-2_mr-0_mb-0_ml-2 { margin: 0.5rem 0 0 0.5rem; } /* ... */ .mt-2_mr-0_mb-1_ml-0 { margin: 0.5rem 0 0.25rem 0rem; } .mt-2_mr-0_mb-1_ml-1 { margin: 0.5rem 0 0.25rem 0.25rem; } .mt-2_mr-0_mb-1_ml-2 { margin: 0.5rem 0 0.25rem 0.5rem; } /* ... */ .mt-48_mr-48_mb-48_ml-48 { margin: 12rem 12rem 12rem 12rem; } If we had to do this in Tailwind, the development build would be like 4gb (instead of a lean and mean 3mb, har har). Sadly, some CSS properties do not decompose into separate properties. One example is transform (although there are proposals for splitting it up!) .awesomeify { transform: translateX(3rem) rotate(90deg) scale(1.5); } If we want to translate an element by X, rotate it by Y, and scale it by Z, there's no way to do it using three separate utility classes. Or is there? A brief introduction to CSS variables CSS custom properties (more commonly referred to as CSS variables) allow you to create a single source of truth for a value and refer to it in other parts of your CSS: :root { --color-brand: #0d84ff; } .btn-primary { background-color: var(--color-brand); } .link { color: var(--color-brand); } The cool things about CSS variables is that unlike in Sass/Less/Stylus/potato, they are computed at run-time rather than at build-time, so they can change. For example, you might set your headline color to black by default: :root { --headline-color: #000; } .headline { color: var(--headline-color); } ...then magically override it white any time it's within a parent with a class of dark: .dark { --headline-color: #fff; } <h1 class="heading">My text is black</h1> <div class="dark"> <h1 class="heading">My text is white</h1> </div> Using CSS variables for partial values This is cool on its own, but what's even cooler is that CSS variables don't have to represent a complete value — they can be used for partial values, too. For example, you can store just the RGB channels of a color in a variable, and stick it into the rgba() function along with an opacity value: :root { --rgb-brand: 13, 132, 255; } .btn-primary--faded { background-color: rgba(var(--rgb-brand), 0.75); } This is how Tailwind's text opacity utilities work. Every text color utility sets a --text-opacity variable to 1, then references it for the alpha channel of an rgba value: .text-blue-300 { --text-opacity: 1; color: rgba(144, 205, 244, var(--text-opacity)); } Then the actual text opacity utilities simply change that variable to another value: .text-opacity-50 { --text-opacity: 0.5; } Since we define the text opacity utilities after the text color utilities, the variable value in the text opacity utility always takes precedence over the value set in the text color utility: <h1 class="text-blue-300 text-opacity-50"> I'm blue at 50% opacity. </h1> Without this technique, it would be impossible to control the color and opacity independently using classes, because all the work needs to be done in one CSS property. Filling "slots" using CSS variables So what about our original issue, trying to make the transform property composable? The way we do it in Tailwind is by creating variables for the various transform features, and combining them together to create the entire transform value. Here's a slightly simplified version (we also let you scale X and Y independently, as well as skew): .transform { --transform-translate-x: 0; --transform-translate-y: 0; --transform-rotate: 0; --transform-scale: 1; transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) scale(var(--transform-scale)); } You'll see that what we're doing here is creating "slots" in the transform value, one for each of: translateX, translateY, rotate, and scale. We fill each slot with a "no-op" value by default, like rotating by 0 degrees, or scaling by a factor of 1. Then we define additional classes that manipulate only the CSS variables: .translate-x-2 { --transform-translate-x: 0.5rem } .translate-y-3 { --transform-translate-x: 1.5rem } .rotate-45 { --transform-rotate: 45deg } .scale-150 { --transform-scale: 1.5; } Again, since those utilities are defined after the transform utility, they override the variables, without actually blowing away the transform property itself: <div class="transform translate-y-3 rotate-45 scale-150"> <!-- ... --> </div> The transform class "enables" transforms, and the classes for each variable just manipulate one of individual transform "slots". This takes a traditionally non-composable property, and makes it perfectly composable using multiple classes in your HTML. Concatenating optional values using empty variables This is where things get really wild. Say you're trying to make something like the font-variant-numeric property composable. It's made up of a bunch of different optional chunks: font-variant-numeric: {ordinal?} {slashed-zero?} {figure-value?} {spacing-value?} {fraction-value?} So all of these are valid: .my-class { font-variant-numeric: ordinal; font-variant-numeric: slashed-zero; font-variant-numeric: ordinal slashed-zero; font-variant-numeric: ordinal tabular-nums; font-variant-numeric: slashed-zero lining-nums; font-variant-numeric: slashed-zero diagonal-fractions; font-variant-numeric: ordinal tabular-nums diagonal-fractions; font-variant-numeric: ordinal slashed-zero oldstyle-nums tabular-nums diagonal-fractions; /* Etc. */ } Taking what you've learned above, you might think to try something like this: .variant-numeric { font-variant-numeric: var(--variant-ordinal) var(--variant-slashed-zero) var(--variant-figure) var(--variant-spacing) var(--variant-fractions); } .ordinal { --variant-ordinal: ordinal; } .slashed-zero { --variant-slashed-zero: slashed-zero; } /* ... */ .stacked-fractions { --variant-fractions: stacked-fractions; } .diagonal-fractions { --variant-fractions: diagonal-fractions; } The problem is that doing things this way, our variables like --variant-ordinal have no default value, which means when var(--variant-ordinal) tries to resolve, it will be what the spec calls the invalid value, which actually invalidates the whole rule, so the CSS isn't applied at all. So you might think, "well maybe we can default to an empty string?" --variant-ordinal: ''; The problem is that's not actually an empty string at all — in CSS that's the literal value ''. Remember, CSS values aren't quoted. So what can we do? Well it turns out whitespace is a valid CSS value. --variant-ordinal: ; Yep, that's not a syntax error, that's perfectly valid CSS that sets --variant-ordinal to a literal space character. Now when var(--variant-ordinal) resolves, we'll get a space, which is totally allowed in a list of values. Here's what a working solution looks like: .variant-numeric { --variant-ordinal: ; --variant-slashed-zero: ; --variant-figure: ; --variant-spacing: ; --variant-fractions: ; font-variant-numeric: var(--variant-ordinal) var(--variant-slashed-zero) var(--variant-figure) var(--variant-spacing) var(--variant-fractions); } .ordinal { --variant-ordinal: ordinal; } .slashed-zero { --variant-slashed-zero: slashed-zero; } /* ... */ .stacked-fractions { --variant-fractions: stacked-fractions; } .diagonal-fractions { --variant-fractions: diagonal-fractions; } The only problem with this is that it probably won't work in your production build. If you're using any kind of CSS minifier, it is almost guaranteed to strip these variables out in my testing. In fact, this is the only thing that actually worked: .variant-numeric { --variant-ordinal: var(--not-a-real-variable,/*!*/ /*!*/); /* ... */ } What the fuck? Yeah I agree. Basically what we're doing here is tricking the minifier into keeping that space that appears between the two comments. We have to put ! in the comments to tell minifiers not the strip them, and we have to do all of that as the default value of a non-existent variable, otherwise the minifiers will see the value as empty and strip the whole declaration. The nice part is this should actually be fixed when PostCSS 8 gains mass adoption, so we'll be able to go back to this slightly less weird looking syntax instead: .variant-numeric { --variant-ordinal: ; /* ... */ } CSS, what a riot.

Tailwind CSS: From Side-Project Byproduct to Multi-Million Dollar Business

This was originally posted as a thread on Twitter, but I thought I'd republish it here to give it a proper home. So about a month or so ago, Tailwind cracked 10 million total installs, which given its humble beginnings, completely blows my mind. We're also about to cross $2 million in revenue from Tailwind UI, our first commercial Tailwind CSS product which was released about 5 months ago — a bit under two years after the very first Tailwind CSS release. Here’s the story from the beginning, while it’s still fresh enough to remember… Reddit meets Pinterest meets Twitter Way back in 2015, I told my now-business-partner Steve Schoger about a side-project idea I had for a site where companies could share interesting links with their team, and outsiders could subscribe to see what teams they admired were reading. We called it “Digest”. We were pretty excited about it, and I decided to take a week off work to build the initial prototype. In classic developer fashion though, I spent the entire week making decisions about the tech stack, and had maybe one full day at the end to actually hack on the real functionality. One of those decisions was what to do about the CSS. I’d always been a big Bootstrap fan, but the first Bootstrap 4 alpha had just come out and they had dropped Less for Sass. I hated Sass! Sass had sort of beat out Less in the preprocessor wars of the mid-2010s, but in my opinion Less was the nicer language. It was functional and declarative, rather than procedural and imperative like Sass, and it had one killer feature Sass didn’t have: classes as mixins. You see in Less, any existing class could automatically be used as a mixin. You didn’t need to explicitly define a mixin like you did in Sass. This let you easily create larger class abstractions out of smaller utility classes. If you've used @apply in Tailwind, this will probably look familiar… So anyways, back to Digest. Normally I would’ve used Bootstrap, but the Sass thing killed it for me. I wanted to keep using Less. So of course my only choice was to author all of the styles from scratch. I spent most of the week on this initial styling system. What I was building was very Bootstrap-inspired, and had lots of component classes like btn, card-list, and radio-box. But it also had this small set of utility classes. (Here’s where we kind of ended up by the way, I think it still looks pretty good even 5 years later!) Anyways, we got busy with other projects, lost enthusiasm for this idea, and ultimately it all landed in the side-project graveyard (as most things do). All but the stylesheets. On every new project, I kept copying and pasting all the Less files from Digest and using them as a starting point, customizing them as necessary to suit whatever new design I was building. I must have brought them across to at least 4 or 5 other projects after we abandoned Digest. I noticed something as I copied the styles across though: the utilities (which started as simple padding and margin utilities) kept growing and evolving, covering more and more CSS features, while the components files kept getting shorter and shorter. The utilities were the only things that were truly “portable”, while the component styles were always too opinionated to reuse on another design. This is when I really started to identify the whole “utility-first” thing as an architectural philosophy, rather than seeing utilities as being just a useful bag of tricks to slap in my HTML here and there as needed. Abandoned side-project #2 Fast-forward a few years later and Steve and I started working on KiteTail, which was going to be a developer-focused, webhook-driven checkout platform: We were taking this one pretty seriously at the time, and — using those old Digest styles as a starting point — I started building the thing, trying very hard to make the styles as “project-agnostic” as possible. You can watch me build a ton of the app on YouTube, and you can see all these utility styles in there the whole time: Watch the "Building KiteTail" series on YouTube Now at this point I had zero intention of maintaining any sort of open-source CSS framework. It didn’t even occur to me that what I had been building would even be interesting to anyone. But stream after stream, people were always asking about the CSS: This is the benefit of working in public — Steve and I would have never built this Tailwind Labs business (which has now done over $4m in revenue in under 2 years) if I hadn’t been live-streaming my work on yet-another-abandoned-side-project. Anyways, eventually I thought “maybe I’ll open-source this little Less framework?” Working on the @kitetailapp Less framework a bit while watching True Lies tonight 😋 Think I'm going to polish this enough to open source 🤘🏻 — Adam Wathan (@adamwathan) June 18, 2017 Going open-source A few people reached out to me about it around this time in case they could collaborate on it with me in any way. One of these people was Stefan Bauer, who if I remember right actually was the person to suggest prefixes like sm:font-bold instead of sm-font-bold for Tailwind's responsive utilities. My good friend Jonathan Reinink also messaged me about the framework around this time, saying he was about to do a big redesign of his SaaS project and wanted to try this crazy utility nonsense I had been blabbering about. This turned out to be key for making the framework actually good, because our projects had completely different designs, and what-would-become-Tailwind needed to support both of those projects. This was a great forcing function for making it project-agnostic. By the way, the name Tailwind? It came from me wanting the name to be tied back to KiteTail in some way, since Steve and I were still pushing hard on that idea, and had dreams of it being this awesome company we’d run one day. I just threw “tail\*” into to see if I could find any cool related words. Back to the story — this was around June/July 2017, and for the next 2-3 months, Jonathan and I worked feverishly on making something that was good enough to open-source. One of the challenges I faced during this process was that in order to make Tailwind as configurable as I wanted, I had to seriously push the boundaries of what was possible with Less, and write some truly cryptic and horrific shit: Writing a test suite for this sort of thing was not really practical as far as I could figure, and it was getting to the point where I didn’t even understand the system anymore and just had to hope and pray that solving one problem didn’t introduce another. This was maybe mid-August, and my friend David Hemphill suggested I mess around with PostCSS and see if I could write the framework in JS. I had no idea what it meant to use PostCSS to build something like Tailwind (I thought it was limited to the sort of things autoprefixer uses it for), but David clued me in to some high-level tricks, like using custom at-rules and custom properties as “hooks” to insert generated code. I started messing around with it and was immediately amazed by how much more confident I felt in the code, and the amazing things I could do given a proper programming language. I talked all about it on Full Stack Radio a few weeks later: Listen to "Building a CSS Framework with PostCSS" on Full Stack Radio (By the way, to this day I feel like Tailwind is completely abusing PostCSS in a way it was never intended, and I secretly believe Andrey Sitnik cringes a little bit every time he thinks about what we’ve done with his beautiful library 😅) Anyways, on Halloween night 2017 we were putting the finishing touches on the first release, and cranking our asses off on the initial documentation: Current @tailwindcss team status: — Adam Wathan (@adamwathan) October 31, 2017 We got it out the door and had tons of positive attention, even for v0.1.0: 🎉 Holy smokes @tailwindcss 0.1.0 has arrived! 😱 — Adam Wathan (@adamwathan) November 1, 2017 A few days later, Andrew Del Prete wrote what would be one of the most important blog posts in the history of the framework, introducing me to the wonderful world of PurgeCSS. Read "Using ~~PurifyCSS~~ PurgeCSS to Remove Unused Tailwind CSS Classes" After about a year of new v0.x releases with lots of cool new features and a growing community, I announced I was going to go full-time on Tailwind CSS. Going full-time on Tailwind I was supposed to be starting on a new SaaS project with a friend, but after the success of Refactoring UI (a book Steve and I had released in December 2018) and the growth of Tailwind, I knew I would regret not pushing it further. Tailwind CSS is by far the highest impact project I’ve ever worked on — it felt like it was this close to being my "dent in the universe", and the idea of not putting in the work to push it over that hump made me sick. I was lucky enough to have a big bankroll from Refactoring UI, and I knew there were ways to build commercial offerings around the framework itself (themes, UI kits, courses, something!) so I decided to go for it. I busted my ass cleaning things up and applying what we’d learned to put together a proper v1.0 release, which came out on May 13, 2019: 🚀 Super excited to announce that @tailwindcss v1.0 is finally out! Head over to the website and give it a spin 🤙🏻 — Adam Wathan (@adamwathan) May 13, 2019 After that, Steve and I went heads down for the rest of the year trying to figure out what on earth “Tailwind the business” was going to be. We prototyped and discarded tons of different ideas, but eventually decided to pursue what is now Tailwind UI. Here’s the first glimpse of that idea, back in March 2019: Been prototyping a @tailwindcss component gallery/studio project I've been planning with @steveschoger this weekend 👀 Think hundreds and hundreds of fully responsive professionally designed components, pre-built so you can just copy the HTML and tweak to taste 👌🏻 Useful? — Adam Wathan (@adamwathan) March 30, 2019 We worked tirelessly on Tailwind UI for months, and finally got our early access release out the door in February 2020, after working literally 36 hours straight before our self-imposed deadline. 🥳 Holy crap Tailwind UI is live! Everything you need to know about getting early access is on the new website 👉🏻 (Lots of free stuff to preview too!) — Adam Wathan (@adamwathan) February 26, 2020 It has been successful beyond our wildest dreams (going to cross $2m in revenue next week), and as a result we’ve been able to start building an amazing team (Brad Cornes, Simon Vrachliotis and mystery developer #3) to keep pushing the future of Tailwind forward. Things are only going to get more incredible from here, and I can’t wait to turn some of the ideas rolling around our heads into new features, products, and tools to make the Tailwind experience even better over the coming years. Thank you for supporting us ❤️

Persistent Layout Patterns in Next.js

When single-page applications were really getting popular in the early Backbone/Ember/Angular days, one of the biggest selling points was that you could navigate around your site without re-rendering the entire document from scratch every time the URL changed. This meant you could do things like preserve the scroll position in part of the UI that didn't change (like a sidebar for example) without the complexity of measuring it and trying to restore it on the next page load like you'd have to do in a traditional server-driven application. Because this benefit was so heavily advertised, I was very surprised to find out that in many modern single-page application frameworks like Next.js and Gatsby, the default experience is re-rendering the entire UI every time you click a link — throwing away that nice feeling of a persistent UI we worked so hard to achieve a decade ago! Next.js is such a wonderfully productive development experience and produces such incredibly fast websites that I just refused to believe it had to be this way. So I spent a few weeks researching, asking questions, and experimenting, and came up with these four patterns for persistent layouts in Next.js applications. The default experience Check out this little demo app I've put together: It's got two main sections: A home screen, which is just a single page. An account settings section, which contains a horizontally scrollable list of tabs that you can click to visit different subsections. In this demo, the page components are constructed in the simplest, most naive way possible, where the pages themselves use the layout components they need as their own root element: // /pages/account-settings/basic-information.js import AccountSettingsLayout from '../../components/AccountSettingsLayout' const AccountSettingsBasicInformation = () => ( <AccountSettingsLayout> <div>{/* ... */}</div> </AccountSettingsLayout> ) export default AccountSettingsBasicInformation The problem with this approach is most obvious when you visit one of the account settings pages, scroll the list of tabs all the way to the right, and click the "Security" tab. Notice how the tab bar jumps all the way back to the left? That's because using this approach, the component at the top of the component tree is always the page component itself, even if the first node in each page component is the same layout component. Because the page component changes, React throws out all of its children and re-renders them from scratch, meaning each page renders a brand new copy of the SiteLayout or AccountSettingsLayout, even if the previous page had already rendered that component in the same place in the DOM. The end result is a single-page application that feels like a server-driven application UI. Yuck! Option 1: Use a single shared layout in a custom `<App>` component One way to add a persistent layout component to your site is to create a custom App component, and always render your site layout above the current page component in the component tree. // /pages/_app_.js import React from 'react' import App from 'next/app' import SiteLayout from './components/SiteLayout' class MyApp extends App { render() { const { Component, pageProps } = this.props return ( <SiteLayout> <Component {...pageProps}></Component> </SiteLayout> ) } } export default MyApp Here's what that looks like when applied to the demo app from earlier: You can confirm that the SiteLayout component is not re-rendered by typing something into the search field, then navigating to another page. Because the SiteLayout component is re-used across page transitions, whatever you type in the search field is preserved — progress! But we still have the tab bar scroll position problem to deal with. That's because using this approach, we can only provide a single layout component and that component must be shared across all pages. That means the wrapper component we add here has to be the lowest common denominator between all pages on the site, and our homepage doesn't use the extra UI from the AccountSettingsLayout so we still have to render that component as the first node in each account settings page: // /pages/account-settings/basic-information.js import AccountSettingsLayout from '../../components/AccountSettingsLayout' const AccountSettingsBasicInformation = () => ( <AccountSettingsLayout> <div>{/* ... */}</div> </AccountSettingsLayout> ) export default AccountSettingsBasicInformation So while this approach might work if you really only need one layout and it's used for every single page on your site, it falls apart very quickly for even the most basic of projects. Option 2: Render a different layout in `<App>` based on the current URL If your needs are only slightly more complex than what's possible with a single shared layout, you might be able to get away with rendering a different tree based on the current URL. By inspecting router.pathname, you can figure out which "section" of your site you're currently in, and render the corresponding layout tree: // /pages/_app_.js import React from 'react' import App from 'next/app' import SiteLayout from '../components/SiteLayout' import AccountSettingsLayout from '../components/AccountSettingsLayout' class MyApp extends App { render() { const { Component, pageProps, router } = this.props if (router.pathname.startsWith('/account-settings/')) { return ( <SiteLayout> <AccountSettingsLayout> <Component {...pageProps}></Component> </AccountSettingsLayout> </SiteLayout> ) } return ( <SiteLayout> <Component {...pageProps}></Component> </SiteLayout> ) } } export default MyApp As long as you make sure you always render the entire layout tree directly in _app.js (don't use SiteLayout from within AccountSettingsLayout for example), this works perfectly: Even the tabs work this way! The downside of this approach is that your App component is going to be constantly churning as you add new sections to your site, and quickly grow out of control if you aren't careful about looking for opportunities to extract the route matching logic. It also couples your layouts to your URLs, and if you ever needed to deviate (maybe /account-settings/delete uses a totally different layout), you need to add more and more hyper-specific conditional logic. So while this can work well for smaller sites, for larger sites you'll want something more declarative and flexible. Option 3: Add a static `layout` property to your page components One way to avoid your App component growing in complexity over time is to move the responsibility of defining the page layout to the page component instead of the App component. You can do this by attaching a static property like layout (the name can be whatever you want) to your page components and reading that from inside your App component: // /pages/account-settings/basic-information.js import AccountSettingsLayout from '../../components/AccountSettingsLayout' const AccountSettingsBasicInformation = () => <div>{/* ... */}</div> AccountSettingsBasicInformation.layout = AccountSettingsLayout export default AccountSettingsBasicInformation // /pages/_app_.js import React from 'react' import App from 'next/app' class MyApp extends App { render() { const { Component, pageProps } = this.props const Layout = Component.layout || (children => <>{children}</>) return ( <Layout> <Component {...pageProps}></Component> </Layout> ) } } export default MyApp Here's what that looks like in action: This is almost perfect, but there's one issue: The search field state is not preserved when navigating from the home page to an account settings page, or vice versa. That's because in this example, the AccountSettingsLayout uses the SiteLayout internally, which means the actual top level layout component is switching from SiteLayout to AccountSettingsLayout, and the original SiteLayout is destroyed and replaced with a new instance created inside of AccountSettingsLayout. If that sort of limitation isn't a problem for your site, this can be a great option. If it is, thankfully there's one more pattern we can try. Option 4: Add a `getLayout` function to your page components If we use a static function instead of a simple property, we can return a complex layout tree instead of a single component: // /pages/account-settings/basic-information.js import SiteLayout from '../../components/SiteLayout' import AccountSettingsLayout from '../../components/AccountSettingsLayout' const AccountSettingsBasicInformation = () => <div>{/* ... */}</div> AccountSettingsBasicInformation.getLayout = page => ( <SiteLayout> <AccountSettingsLayout>{page}</AccountSettingsLayout> </SiteLayout> ) export default AccountSettingsBasicInformation Then in our custom App component, we can invoke that function passing in the current page to get back the entire tree: // /pages/_app.js import React from 'react' import App from 'next/app' class MyApp extends App { render() { const { Component, pageProps, router } = this.props const getLayout = Component.getLayout || (page => page) return getLayout(<Component {...pageProps}></Component>) } } export default MyApp (I've used the name getLayout in this example but it can be whatever you want — this isn't a framework feature or anything.) This puts each page component in charge of its entire layout, and allows an arbitrary degree of UI persistence: Bonus: Add a `getLayout` function to your layout components Using this approach can mean some repetitive code in page components that use the same layout tree, and can force you to update many files if you ever need to change that tree. One approach I've been experimenting with to avoid this duplication is to add a getLayout function to each layout component as well, so page components can delegate to the layout in order to fetch the complete layout tree. // /components/SiteLayout.js const SiteLayout = ({ children }) => <div>{/* ... */}</div> export const getLayout = page => <SiteLayout>{page}</SiteLayout> export default SiteLayout // /components/AccountSettingsLayout.js import { getLayout as getSiteLayout } from './SiteLayout' const AccountSettingsLayout = ({ children }) => <div>{/* ... */}</div> export const getLayout = page => getSiteLayout(<AccountSettingsLayout>{page}</AccountSettingsLayout>) export default AccountSettingsLayout Now each page component simply imports getLayout from the layout it needs, and re-exports it as a static property on the page itself: // /pages/account-settings/basic-information.js import SiteLayout from '../../components/SiteLayout' import { getLayout } from '../../components/AccountSettingsLayout' const AccountSettingsBasicInformation = () => <div>{/* ... */}</div> AccountSettingsBasicInformation.getLayout = getLayout export default AccountSettingsBasicInformation Here's what this looks like applied to the entire demo: Eventually I'd love to see persistent layouts get more love as a first-class feature in Next.js, but for now hopefully these patterns will let you recreate that classic SPA user experience without having to give up all of the wonderful things Next has to offer. If you have any questions or ideas, I'm @adamwathan on Twitter!

Going Full-Time on Tailwind CSS

Late into Halloween night in 2017, we released the very first version of Tailwind CSS — a utility-first CSS framework for rapidly developing custom user interfaces. Since then: We've published 30 releases 77 people have contributed to the codebase The GitHub repository has been starred over 8,000 times Over 1,100 people joined the Tailwind CSS Slack community Over 10,000 people started following @tailwindcss on Twitter The framework has been installed almost 700,000 times via npm What started as just a bunch of boilerplate I was copying and pasting between new projects has grown into a serious tool that has been adopted by some of my favorite companies, including Algolia who generously power the Tailwind documentation search. Algolia's new documentation site, built with Tailwind CSS. Last summer I was having a difficult time figuring out what I really wanted to be working on for the long haul. For the past few years I've been pretty successfuly creating books and courses for other developers, but every individual project felt really short-term — just something to work on for a couple of months, leaving me to (hopefully) come up with a new idea to work on when it was finished. Not only that, but every project felt sort of disconnected and isolated: what could a video course on TDD and a CSS framework possibly have in common? Eventually though, after re-reading Anything You Want by Derek Sivers for the nth time, I realized there actually was a common thread between everything I spend my time on: I like to help people build awesome software, and have more fun doing it. The more I've heard from people using Tailwind CSS, the more I've realized it has the potential to be the highest impact project I've ever worked on in terms of fulfilling that goal. So starting in 2019, I'm going to be working full-time on Tailwind CSS. Here are some of the things I'm going to be working on. Getting to version 1.0 Tailwind has been out in the wild for over a year now, so the rough edges have become a bit more obvious and the vision for the project has continued to become more clear. The first month or so of 2019 is going to be dedicated to polishing what we have with v0.7.3 and releasing a proper v1.0.0. Overall I don't expect a ton of things to change, but the areas I'm paying the most attention to include: Consistency in both class and configuration naming Improving default configuration scales (the max-width scale could be better for example) Making any breaking changes that might be necessary to support planned future features As I've always done my best to do in the past, the upgrade path will be as simple, clear, and painless as possible. You might need to change a couple of class names in your markup, but even with changes like that I'm planning to provide official plugins to provide backwards compatibility so you can upgrade progressively if needed. There will be a v1.0.0 release in February for sure. Expanding the documentation People always tell me our documentation is awesome but there are still a ton of pages with "Work in Progress!" banners I need to finish. Fully finishing the API documentation will be the first thing I work on after tagging v1.0. After that, I'm planning to add more tutorial-style documentation (like our page on Extracting Components), a lot more component examples, and a dedicated knowledge base area where I can publish articles that answer common questions that don't fit into the structure of the regular documentation. Some of the areas I'm planning to expand the documentation include: More instructions for integrating Tailwind with different frameworks and build tools (setting up Tailwind with vue-cli, nuxt.js, create-react-app, next.js, Ember, Rails, Phoenix, etc.) Explaining browser support Explaining our Preflight styles Best practices for adding your own base styles Building themeable interfaces with Tailwind Sharing a Tailwind config between projects ...and a bunch more. Video Tutorials Once 1.0 is out and the documentation feels solid, I'm going to be working on an official video course called "Designing with Tailwind CSS". It will of course cover getting set up with Tailwind and explaining the features and workflow, but the bulk of it is going to focus on actually building great looking interfaces with Tailwind. By working through a bunch of different layout and component examples, you'll learn the intricacies of CSS positioning, how to effectively use flexbox, best practices for responsive design, and a ton more. It will be completely free, and I'm hoping to have it ready around April or May. Growing the community Up until now we've used the tailwindcss/discuss repository as a makeshift discussion forum and even though it works okay, I'd like to launch a proper Discourse forum in the new year. I'm planning to spend a few hours every week making sure everyone's questions are answered, and hopefully my own activity in the forums will help foster a bigger and more active community where questions and answers don't disappear like they do in Slack. I'm also planning to move our Slack community over to Discord after the success other communities like Statamic and EmberJS have had with it so we'll still have a great place for real-time chat. Showcasing awesome Tailwind CSS projects Right now there are a few community-driven projects like awesome-tailwindcss, Built with Tailwind, and TailwindComponents that do a great job curating beautiful Tailwind sites and third-party plugins, but I think putting together something official would go a long way towards increasing adoption. So I'm planning to add a new section to the website that links out to third-party plugins and tools built by the community, as well as a gallery that showcases websites that are built with Tailwind. New features and tools Of course I'll be continuing to develop new features for future Tailwind releases, initially focusing on some highly requested additions like: CSS Grid Transitions and animations Making utility variants like "hover" work with "@apply" I've also got some ideas for additional tooling I'd like to build, like: A style guide generator powered by your Tailwind config file A playground/sandbox for creating JSFiddle/CodePen-style demos but with access to all of Tailwind's features A tool for building color palettes I don't have a timeline in mind for any of that stuff, but they are ideas I'm tossing around nonetheless. Themes and UI kits Out of the box Tailwind is perfect for people with a good sense for design and a solid background in CSS, but there's a huge group of people out there who want to use Tailwind but would benefit from a more opinionated starting point. Over the spring and summer I'm going to be working with Steve Schoger to put together some official themes and UI kits that will help even more people get started with Tailwind by providing predesigned HTML-based components and layouts that look great out of the box but are easy to customize. Sustainability Right now I'm fortunate enough to be able to work on Tailwind full-time for a while without worrying about paying my bills, but of course that can't last forever. Here are some of the ideas I have for making this work over the long haul: Continue running my book/course business My existing products continue to sell pretty well even when I'm focused on other things, but inevitably I'll have to put some work into that business to make sure it continues to fund my time on Tailwind. I'm planning to release an updated version of my Test-Driven Laravel course in 2020, as well as an updated version of Advanced Vue Component Design when Vue 3.0 is released. Corporate sponsorship Much like Evan has done with Vue.js and Taylor has done with Laravel, I think there is potential to fund a lot of Tailwind development through corporate sponsorship, especially once v1.0 is out and if I can continue to grow the user base. I'm not going to explore this seriously any time soon, but it might be something to consider later in the year once my other efforts start to pay off. Premium products Things like the themes and UI kits are great candidates for paid products, much like Bootstrap does with their official theme store. This is something I'll definitely be thinking about once we get deeper into that project. Overall I'm pretty optimistic about making this all work, but only time will tell for sure. Either way, I'm going to give it a shot for all of 2019 and see what I can do. It'll be an exciting year for sure.

2018 Year in Review

Something I've struggled with in the past is looking back on the previous year and wondering "what did I even get done?" and "did I really make the best of my time?" It's easy to stress about the things you didn't get done, and even easier to forget about the things you did, so this year I've decided to start a tradition of capturing it all in one place to avoid being so dismissive of my own accomplishments. Personal The biggest personal change of the year was moving out of the first house I bought 10 years ago. I bought that house before I met my wife, so even though we lived there together for three years, it was great to be able to buy a new house together that was really ours instead of mine. We bought a much bigger, much newer house (only a year old) but in the same neighborhood, so it was an easy move and nothing changed in terms of the amenities we're familiar with or being close to friends and family. We got to move in just in time for my daughter's first birthday and throw her a big party which was a lot of fun. The housing market has gone up a ton since I bought my first house in this neighborhood, so I was really hesitant about moving at first. We had a crazy low mortgage compared to anyone buying in our neighborhood now, why give that up and get ourselves into something a lot more expensive when it felt like we had such a huge advantage? But after being in the new place for about five months now, I'm glad we did it. It's our dream home and we'll probably never move again, so if we were ever going to move, I'm glad we did it now before things got even more expensive. Business 2018 was the best year yet for my book/course business, and I was able to wrap up three projects. Test-Driven Laravel Although Test-Driven Laravel was initially released in early access in November 2016 (!), I didn't publish the final lesson until January 2018. Getting this done was a huge weight off my shoulders after grinding through lesson after lesson for all of 2017. The take-away for me is that pre-selling is not for me. I am reliably way too optimistic and ambitious with every new project I start, and by selling early access to the course, I made it impossible to change the scope without letting people down. I'm really proud of how it all turned out but man, it was a brutal process. Advanced Vue Component Design Learning my lesson from Test-Driven Laravel, I brought Advanced Vue Component Design from idea to launch in less than two months, releasing it on May 9th. Cranking the whole thing out in such a condensed time frame meant that my energy and motivation was high the entire time, and I didn't burn myself out like I did with the TDD course. It did incredibly well, and I think the final product is awesome. Refactoring UI In the summer, I partnered with my good friend Steve Schoger to create Refactoring UI — a resource for developers who want their projects to look awesome but don't have access to a talented designer. Writing the book and working on the additional resources was more or less my full-time job from July through to launching it in December. I did all of the writing, but the content is so image-heavy that Steve definitely still shouldered more of the work. This was a really huge project and we were both pretty toast by the end of it. I don't think we would've done anything differently in hindsight, but I can say that neither of us would ever want to take on something bigger than this in the future — it was right at our limit for energy and motivation for sure. The launch was the best I've ever had, selling over 6000 copies in the first two weeks. Realistically I don't see how I'm ever going to beat this year without trying to scale the business in ways that just don't interest me, but that's okay. The business earned way more than enough, and as long as it does better than enough next year, that will be a success in my books. Conference speaking This year I gave talks at five conferences in three countries. Laracon Online 2018: Advanced Vue Component Design In February I gave a talk at Laracon Online about building renderless UI components with Vue. The video isn't available yet publicly, but a few weeks after the conference I put together a massive blog post covering all of the same content. MicoConf 2018 Starter Edition: Nailing Your First Launch In May I spoke at MicroConf about my approach to successfully launching products like Refactoring to Collections and Test-Driven Laravel. This was cool for me because it was the first time I've ever given a talk that wasn't about writing code. You can check out a video and a great summary of the talk over at Christian Genco's MicroConf Recap site. Laracon US 2018: Resisting Complexity In July I gave a talk at Laracon US in Chicago about some of the mistakes I think people make with object oriented programming, and how thinking about methods in a different way can lead to simpler code. You can check out the video on my YouTube channel. Laracon AU 2018: Resisting Complexity In October, I made the trek with my wife and 14 month old daughter all the way to Australia to give my "Resisting Complexity" talk at the inaugural Laracon AU. A 22 hour flight with a baby is just as bad as it sounds, and dealing with a jet-lagged baby is even worse. Never do this 😅 Video of the talk is available on the Laracon AU YouTube channel. VueConf TO: Advanced Vue Component Design In November I spoke at VueConf TO in Toronto, which is only about an hour away from where I live. Although the talk had the same title as the one I gave at Laracon Online in February, it was a different talk and focused on using scoped slots to build what I've been calling "data provider components" to do things like simulate element queries, inform a component if it has entered the viewport, or provide a component with data from an external resource. There's no video of this talk (yet?) but you can check out the source code for the demos on GitHub. I do enjoy speaking and I think all of these talks went really well, but overall this is more speaking than I'd like to do in 2019. With at least a few days spent at each conference, plus travel time, plus preparation time, doing five talks easily chewed up close to two months of the year, which is a ton of time. I've already got two speaking events scheduled for 2019, but I'll probably only do three in total. Writing In 2018 I only published two articles: Renderless Components in Vue.js and 7 Practical Tips for Cheating at Design. On the surface this feels disappointing, but both of these posts were actually a huge investment and ended up being incredibly popular. The design tips article is actually the fifth most popular Medium story of all time, at least at the time of this post. Overall I'm happy with this — I'd rather write a couple of really solid in-depth posts per year than put out lower quality articles every week or two. I did also write an entire book this year, so I should probably give myself some credit for that, too. Live streaming Something I've really enjoyed over the last few years and have tried to do more regularly is live coding on YouTube. I'm pretty good at speaking while I'm coding, so for me it's a great way to create content without doing a ton of preparation or post-production. This year I did 18 live streams in total, falling into three main categories: Building Components with Vue.js In March I live streamed myself building a ConvertKit opt-in form with Vue, and in April I built a drag-and-drop sorting component that wrapped up Shopify's Draggable library. Building Interfaces with Tailwind CSS People seem to really love watching me build different things with Tailwind, so I tried to do a few of those year, including: Building Rebuilding Netlify Rebuilding (and part two) I'd like to keep doing more of these in 2019. Building SponsorShip In June I had this crazy idea to try and build an entire MVP in one day, and live stream the entire thing. Watch "Building SponsorShip" I didn't get it all done on the first day, and ended up taking two full days to get it to where I wanted. I split it up into 12 sessions which are all available to rewatch on YouTube. I'm hoping to do more live streaming in 2019, and hopefully on a more regular schedule like my podcast. Podcasting I published 25 new Full Stack Radio episodes in 2018, and cracked 2 million total downloads. The three most popular episodes were: 81: Evan You - Advanced Vue Component Design (32,160 downloads) 80: Tom Schlick - Building Multitenant Applications (30,891 downloads) 83: DHH - Stimulus in Practice + On Writing Software Well (29,214 downloads) The show is consistently doing over 80,000 downloads per month now which is amazing, and I think a testament to slow and steady consistency. I've been doing it for four years now, and even though it's not my number one priority by any means, I manage to get a new episode out every two weeks and the audience continues to grow. Tailwind CSS We published the first Tailwind CSS release on Oct 31, 2017, so 2018 was the first full year that I've developed and maintained it. I put out 17 releases in 2018, and the framework has now been installed 670,000 times. We've also got over 8,000 stars on GitHub, over 10,000 followers on Twitter, and over 1,000 members in Slack now! The biggest new Tailwind feature in 2018 was definitely the plugin system, which makes it even easier to extend and customize the framework. A ton of awesome Tailwind projects were shared with me throughout the year, but two of my favorites that really make me feel like we've made it are the new Algolia documentation site, and the new Laracasts redesign. Plans for 2019 So it turns out I did a lot of stuff this year! Here are a couple of things I've got planned for next year: Regular work journaling Like I mentioned at the beginning of this post, something I have struggled with in the past is giving myself credit for the things I've done, and focusing too much on the things I still want to do. To try and avoid that going forward, I'm planning to put together a short "week in review" post every week where I can write about what I'm working on and what I got done. I'm going to be posting them here on my website for everyone to read, in a dedicated section so they don't get mixed up with my more in-depth articles. With any luck I'll have all of that ready to go for the first week of January. Going full-time on Tailwind CSS I think Tailwind has the potential to be the most impactful thing I do in the web development world, and I want to make the most of that opportunity. So 2019 is going to be the year where I really double-down on it, and do everything I can think of to grow the user-base and make it the best tool it can possibly be, with the best ecosystem I can possibly create. I have a ton to say about this but it's best left for its own post. Look for it in the next few days! 2019 is going to be awesome.

The Trick to Understanding Scoped Slots in Vue.js

Scoped slots are one of Vue's most powerful features, but it can be a bit tricky to understand how they work when first learning about them. In this screencast (taken from my Advanced Vue Component Design course), I walk through how thinking of scoped slots as function props can make it a lot easier to wrap your head around them. Learning More If you enjoyed this screencast, check out Advanced Vue Component Design, a video series I'm working on that goes deep into tons of useful component design patterns. Visit the website to learn more or subscribe below for periodic updates, more free screencasts, and a big discount when the course is released this May: You're subscribed! Something went wrong, please try again.

Renderless Components in Vue.js

Have you ever pulled in a third-party UI component only to discover that because of one small tweak you need to make, you have to throw out the whole package? Custom controls like dropdowns, date pickers, or autocomplete fields can be very complex to build with a lot of unexpected edge cases to deal with. There are a lot of libraries out there that do a great job handling this complexity, but they often come with a deal-breaking downside: it's hard or impossible to customize how they look. Take this tags input control for example: See the Pen Reusable Vue Components with Scoped Slots: Traditional Tags Input by Adam Wathan (@adamwathan) on CodePen. This component wraps up a few interesting behaviors: It doesn't let you add duplicates It doesn't let you add empty tags It trims whitespace from tags Tags are added when the user presses enter Tags are removed when the user clicks the × icon If you needed a component like this in your project, pulling this in as a package and offloading that logic would certainly save you some time and effort. But what if you needed it to look a little different? This component has all of the same behavior as the previous component, but with a significantly different layout: See the Pen Reusable Vue Components with Scoped Slots: Traditional Tags Input w/Alternate Layout by Adam Wathan (@adamwathan) on CodePen. You could try and support both of these layouts with a single component through a combination of whacky CSS and component configuration options, but (thankfully) there's a better way. Scoped Slots In Vue.js, slots are placeholder elements in a component that are replaced by content passed in by the parent/consumer: <!-- Card.vue --> <template> <div class="card"> <div class="card-header"> <slot name="header"></slot> </div> <div class="card-body"> <slot name="body"></slot> </div> </div> </template> <!-- Parent/Consumer --> <card> <h1 slot="header">Special Features</h1> <div slot="body"> <h5>Fish and Chips</h5> <p>Super delicious tbh.</p> </div> </card> <!-- Renders: --> <div class="card"> <div class="card-header"> <h1>Special Features</h1> </div> <div class="card-body"> <div> <h5>Fish and Chips</h5> <p>Super delicious tbh.</p> </div> </div> </div> Scoped slots are just like regular slots but with the ability to pass parameters from the child component up to the parent/consumer. Regular slots are like passing HTML to a component; scoped slots are like passing a callback that accepts data and returns HTML. Parameters are passed up to the parent by adding props to the slot element in the child component, and the parent accesses these parameters by destructuring them out of the special slot-scope attribute. Here's an example of a LinksList component that exposes a scoped slot for each list item, and passes the data for each item back to the parent through a :link prop: <!-- LinksList.vue --> <template> <!-- ... --> <li v-for="link in links"> <slot name="link" + :link="link" ></slot> </li> <!-- ... --> </template> <!-- Parent/Consumer --> <links-list> <a slot="link" + slot-scope="{ link }" :href="link.href" >{{ link.title }}</a> </links-list> By adding the :link prop to the slot element in the LinksList component, the parent can now access it through the slot-scope and make use of it in its slot template. Types of Slot Props You can pass anything to a slot, but I find it useful to think of every slot prop as belonging to one of three categories. Data The simplest type of slot prop is just data: strings, numbers, boolean values, arrays, objects, etc. In our links example, link is an example of a data prop; it's just an object with some properties: <!-- LinksList.vue --> <template> <!-- ... --> <li v-for="link in links"> <slot name="link" :link="link" ></slot> </li> <!-- ... --> </template> <script> export default { data() { return { links: [ { href: 'http://...', title: 'First Link', bookmarked: true }, { href: 'http://...', title: 'Second Link', bookmarked: false }, // ... ] } } } </script> The parent can then render that data or use it to make decisions about what to render: <!-- Parent/Consumer --> <links-list> <div slot="link" slot-scope="{ link }"> <star-icon v-show="link.bookmarked"></star-icon> <a :href="link.href"> {{ link.title }} </a> </div> </links-list> Actions Action props are functions provided by the child component that the parent can call to invoke some behavior in the child component. For example, we could pass a bookmark action to the parent that bookmarks a given link: <!-- LinksList.vue --> <template> <!-- ... --> <li v-for="link in links"> <slot name="link" :link="link" + :bookmark="bookmark" ></slot> </li> <!-- ... --> </template> <script> export default { data() { // ... }, + methods: { + bookmark(link) { + link.bookmarked = true + } + } } </script> The parent could invoke this action when the user clicks a button next to an un-bookmarked link: <!-- Parent/Consumer --> <links-list> - <div slot="link" slot-scope="{ link }"> + <div slot="link" slot-scope="{ link, bookmark }"> <star-icon v-show="link.bookmarked"></star-icon> <a :href="link.href">{{ link.title }}</a> + <button v-show="!link.bookmarked" @click="bookmark(link)">Bookmark</button> </div> </links-list> Bindings Bindings are collections of attributes or event handlers that should be bound to a specific element using v-bind or v-on. These are useful when you want to encapsulate implementation details about how interacting with a provided element should work. For example, instead of making the consumer handle the v-show and @click behaviors for the bookmark button themselves, we could provide a bookmarkButtonAttrs binding and a bookmarkButtonEvents binding that move those details into the component itself: <!-- LinksList.vue --> <template> <!-- ... --> <li v-for="link in links"> <slot name="link" :link="link" :bookmark="bookmark" + :bookmarkButtonAttrs="{ + style: [ link.bookmarked ? { display: none } : {} ] + }" + :bookmarkButtonEvents="{ + click: () => bookmark(link) + }" ></slot> </li> <!-- ... --> </template> Now if the consumer prefers, they can apply these bindings to the bookmark button blindly without having to know what they actually do: <!-- Parent/Consumer --> <links-list> - <div slot="link" slot-scope="{ link, bookmark }"> + <div slot="link" slot-scope="{ link, bookmarkButtonAttrs, bookmarkButtonEvents }"> <star-icon v-show="link.bookmarked"></star-icon> <a :href="link.href">{{ link.title }}</a> - <button v-show="!link.bookmarked" @click="bookmark(link)">Bookmark</button> + <button + v-bind="bookmarkButtonAttrs" + v-on="bookmarkButtonEvents" + >Bookmark</button> </div> </links-list> Renderless Components A renderless component is a component that doesn't render any of its own HTML. Instead it only manages state and behavior, exposing a single scoped slot that gives the parent/consumer complete control over what should actually be rendered. A renderless component renders exactly what you pass into it, without any extra elements: <!-- Parent/Consumer --> <renderless-component-example> <h1 slot-scope="{}"> Hello world! </h1> </renderless-component-example> <!-- Renders: --> <h1>Hello world!</h1> So why is this useful? Separating Presentation and Behavior Since renderless components only deal with state and behavior, they don't impose any decisions about design or layout. That means that if you can figure out a way to move all of the interesting behavior out of a UI component like our tags input control and into a renderless component, you can reuse the renderless component to implement any tags input control layout. Here's both tag input controls, but this time backed by a single renderless component: See the Pen Reusable Vue Components with Scoped Slots: Headless Tags Input w/Multiple Layouts by Adam Wathan (@adamwathan) on CodePen. So how does this work? Renderless Component Structure A renderless component exposes a single scoped slot where the consumer can provide the entire template they want to render. The basic skeleton of a renderless component looks like this: Vue.component('renderless-component-example', { // Props, data, methods, etc. render() { return this.$scopedSlots.default({ exampleProp: 'universe', }) }, }) It doesn't have a template or render any HTML of its own; instead it uses a render function that invokes the default scoped slot passing through any slot props, then returns the result. Any parent/consumer of this component can destructure exampleProp out of the slot-scope and use it in its template: <!-- Parent/Consumer --> <renderless-component-example> <h1 slot-scope="{ exampleProp }"> Hello {{ exampleProp }}! </h1> </renderless-component-example> <!-- Renders: --> <h1>Hello universe!</h1> A Worked Example Let's walkthrough building a renderless version of the tags input control from scratch. We'll start with a blank renderless component that passes no slot props: /* Renderless Tags Input Component */ export default { render() { return this.$scopedSlots.default({}) }, } ...and a parent component with a static, non-interactive UI that we pass in to the child component's slot: <!-- Parent component --> <template> <renderless-tags-input> <div slot-scope="{}" class="tags-input"> <span class="tags-input-tag"> <span>Testing</span> <span>Design</span> <button type="button" class="tags-input-remove">&times;</button> </span> <input class="tags-input-text" placeholder="Add tag..."> </div> </renderless-tags-input> </template> <script> export default {} </script> Piece by piece, we'll make this component work by adding state and behavior to the renderless component and exposing it to our layout through the slot-scope. Listing tags First let's replace the static list of tags with a dynamic list. The tags input component is a custom form control so like in the original example, the tags should live in the parent and be bound to the component using v-model. We'll start by adding a value prop to the component and passing it up as a slot prop named tags: /* Renderless Tags Input Component */ export default { + props: ['value'], render() { - return this.$scopedSlots.default({}) + return this.$scopedSlots.default({ + tags: this.value, + }) }, } Next, we'll add the v-model binding in the parent, fetch the tags out of the slot-scope, and iterate over them with v-for: <!-- Parent component --> <template> - <renderless-tags-input> + <renderless-tags-input v-model="tags"> - <div slot-scope="{}" class="tags-input"> + <div slot-scope="{ tags }" class="tags-input"> <span class="tags-input-tag"> - <span>Testing</span> - <span>Design</span> + <span v-for="tag in tags">{{ tag }}</span> <button type="button" class="tags-input-remove">&times;</button> </span> <input class="tags-input-text" placeholder="Add tag..."> </div> </renderless-tags-input> </template> <script> export default { + data() { + return { + tags: ['Testing', 'Design'] + } + } } </script> This slot prop is a great example of a simple data prop. Removing tags Next let's remove a tag when clicking the × button. We'll add a new removeTag method to our component, and pass a reference to that method up to the parent as a slot prop: /* Renderless Tags Input Component */ export default { props: ['value'], + methods: { + removeTag(tag) { + this.$emit('input', this.value.filter(t => t !== tag)) + } + }, render() { return this.$scopedSlots.default({ tags: this.value, + removeTag: this.removeTag, }) }, }) Then we'll add a @click handler to the button in the parent that calls removeTag with the current tag: <!-- Parent component --> <template> <renderless-tags-input> - <div slot-scope="{ tags }" class="tags-input"> + <div slot-scope="{ tags, removeTag }" class="tags-input"> <span class="tags-input-tag"> <span v-for="tag in tags">{{ tag }}</span> - <button type="button" class="tags-input-remove">&times;</button> + <button type="button" class="tags-input-remove" + @click="removeTag(tag)" + >&times;</button> </span> <input class="tags-input-text" placeholder="Add tag..."> </div> </renderless-tags-input> </template> <script> export default { data() { return { tags: ['Testing', 'Design'] } } } </script> This slot prop is an example of an action prop. Adding new tags on enter Adding new tags is a bit trickier than the last two examples. To understand why, let's look at how it would be implement in a more traditional component: <template> <div class="tags-input"> <!-- ... --> <input class="tags-input-text" placeholder="Add tag..." @keydown.enter.prevent="addTag" v-model="newTag" > </div> </template> <script> export default { props: ['value'], data() { return { newTag: '', } }, methods: { addTag() { if (this.newTag.trim().length === 0 || this.value.includes(this.newTag.trim())) { return } this.$emit('input', [...this.value, this.newTag.trim()]) this.newTag = '' }, // ... }, // ... } </script> We keep track of the new tag (before it's been added) in a newTag property, and we bind that property to the input using v-model. Once the user presses enter, we make sure the tag is valid, add it to the list, then clear out the input field. The question here is how do we pass a v-model binding through a scoped slot? Well if you've dug into Vue deep enough, you might know that v-model is really just syntax sugar for a :value attribute binding, and an @input event binding: <input class="tags-input-text" placeholder="Add tag..." @keydown.enter.prevent="addTag" - v-model="newTag" + :value="newTag" + @input="(e) => newTag =" > That means we can handle this behavior in our renderless component by making a few changes: Add a local newTag data property to the component Pass back an attribute binding prop that binds :value to newTag Pass back an event binding prop that binds @keydown.enter to addTag and @input to update newTag /* Renderless Tags Input Component */ export default { props: ['value'], + data() { + return { + newTag: '', + } + }, methods: { + addTag() { + if (this.newTag.trim().length === 0 || this.value.includes(this.newTag.trim())) { + return + } + this.$emit('input', [...this.value, this.newTag.trim()]) + this.newTag = '' + }, removeTag(tag) { this.$emit('input', this.value.filter(t => t !== tag)) } }, render() { return this.$scopedSlots.default({ tags: this.value, removeTag: this.removeTag, + inputAttrs: { + value: this.newTag, + }, + inputEvents: { + input: (e) => { this.newTag = }, + keydown: (e) => { + if (e.keyCode === 13) { + e.preventDefault() + this.addTag() + } + } + } }) }, } Now we just need to bind those props to the input element in the parent: <template> <renderless-tags-input> - <div slot-scope="{ tags, removeTag }" class="tags-input"> + <div slot-scope="{ tags, removeTag, inputAttrs, inputEvents }" class="tags-input"> <span class="tags-input-tag"> <span v-for="tag in tags">{{ tag }}</span> <button type="button" class="tags-input-remove" @click="removeTag(tag)" >&times;</button> </span> - <input class="tags-input-text" placeholder="Add tag..."> + <input class="tags-input-text" placeholder="Add tag..." + v-bind="inputAttrs" + v-on="inputEvents" + > </div> </renderless-tags-input> </template> <script> export default { data() { return { tags: ['Testing', 'Design'] } } } </script> Adding new tags explicitly In our current layout, the user adds a new tag by typing it in the field and hitting the enter key. But it's easy to imagine a scenario where someone might want to provide a button that the user can click to add the new tag as well. Making this possible is easy, all we need to do is pass a reference to our addTag method to the slot scope as well: /* Renderless Tags Input Component */ export default { // ... methods: { addTag() { if (this.newTag.trim().length === 0 || this.value.includes(this.newTag.trim())) { return } this.$emit('input', [...this.value, this.newTag.trim()]) this.newTag = '' }, // ... }, render() { return this.$scopedSlots.default({ tags: this.value, + addTag: this.addTag, removeTag: this.removeTag, inputAttrs: { // ... }, inputEvents: { // ... } }) }, } When designing renderless components like this, it's better to err on the side of "too many slot props" than too few. The consumer only needs to destructure out the props they actually need, so there's no cost to them if you give them a prop they aren't going to use. Working Demo Here's a working demo of the renderless tags input component that we've built so far: See the Pen Reusable Vue Components with Scoped Slots: Renderless Tags Input by Adam Wathan (@adamwathan) on CodePen. The actual component contains no HTML, and the parent where we define the template contains no behavior. Pretty neat right? An Alternate Layout Now that we have a renderless version of the tags input control, we can easily implement alternative layouts by writing whatever HTML we want and applying the provided slot props to the right places. Here's what it would look like to implement the stacked layout from the beginning of the article using our new renderless component: See the Pen Reusable Vue Components with Scoped Slots: Renderless Tags Input w/Alternate Layout by Adam Wathan (@adamwathan) on CodePen. Creating Opinionated Wrapper Components You might look some of these examples and think, "wow, that sure is a lot of HTML to write every time I need to add another instance of this tags component!" and you'd be right. It's definitely a lot more work to write this whenever you need a tags input: <renderless-tags-input v-model="tags"> <div class="tags-input" slot-scope="{ tags, removeTag, inputAttrs, inputEvents }"> <span class="tags-input-tag" v-for="tag in tags"> <span>{{ tag }}</span> <button type="button" class="tags-input-remove" @click="removeTag(tag)">&times;</button> </span> <input class="tags-input-text" placeholder="Add tag..." v-on="inputEvents" v-bind="inputAttrs"> </div> </renderless-tags-input> ...than this, which is what we started with in the beginning: <tags-input v-model="tags"></tags-input> There's an easy fix though: create an opinionated wrapper component! Here's what it looks like to write our original <tags-input> component in terms of the renderless tags input: <!-- InlineTagsInput.vue --> <template> <renderless-tags-input :value="value" @input="(tags) => { $emit('input', tags) }"> <div class="tags-input" slot-scope="{ tag, removeTag, inputAttrs, inputEvents }"> <span class="tags-input-tag" v-for="tag in tags"> <span>{{ tag }}</span> <button type="button" class="tags-input-remove" @click="removeTag(tag)">&times;</button> </span> <input class="tags-input-text" placeholder="Add tag..." v-bind="inputAttrs" v-on="inputEvents" > </div> </renderless-tags-input> </template> <script> export default { props: ['value'], } </script> Now you can use that component in one line of code anywhere you need that particular layout: <inline-tags-input v-model="tags"></inline-tags-input> Getting Crazy Once you realize that a component doesn't have to render anything and can instead be responsible solely for providing data, there's no limit to the type of behavior you can model with a component. For example, here's a fetch-data component that takes a URL as a prop, fetches JSON from that URL, and passes the response back to the parent: See the Pen Vue.js Fetch Component by Adam Wathan (@adamwathan) on CodePen. Is this the right way to make every AJAX request? Probably not, but it's certainly interesting! Conclusion Splitting a component into a presentational component and a renderless component is an extremely useful pattern to master and can make code reuse a lot easier, but it's not always worth it. Use this approach if: You're building a library and you want to make it easy for users to customize how your component looks You have multiple components in your project with very similar behavior but different layouts Don't go down this path if you're working on a component that's always going to look the same everywhere it's used; it's considerably simpler to keep everything in a single component if that's all you need. Learning More If you enjoyed this post, you might be interested in Advanced Vue Component Design, a video series I put together that goes deep into tons of useful component design patterns. Check out the website to learn more, or sign up below to watch two free preview lessons: You're subscribed! Something went wrong, please try again.

Don't Use Em for Media Queries

In March 2016, Zell Liew published a really great in-depth post titled "PX, EM, or REM Media Queries?", where he shared detailed tests he had performed to figure out which unit was the best choice for media queries and ultimately recommended em units. I've always used pixels for media queries, so although I was pretty convinced I should switch after reading Zell's post, I wanted to do a bit of testing on my own to make sure I really understood the problems with pixels before I renounced them forever. The Problem The problem Zell discovered in his tests is that when you zoom in on Safari, px media queries and em media queries trigger at different sizes. Here I've got two containers set to change their background color at 400px and 25em respectively, with the zoom set to normal (100%): Link to Fiddle In this example you can see that the backgrounds change at the same time, because 25em at the default font size of 16px matches the 400px breakpoint. In this example, I'm using the same containers and breakpoints, but Safari is zoomed to 150%: Link to Fiddle (don't forget to zoom!) Here it's clear that Safari is triggering the pixel breakpoint much sooner than it triggers the em breakpoint. The Mistake In Zell's post, he concluded that Safari was not scaling the pixel breakpoint as the user zooms, and that when using pixels, Safari was triggering the breakpoint too soon. But let's measure when the actual breakpoints are triggered: Interesting! Safari is triggering the 400px breakpoint at 600px when zoomed to 150% (the correct behavior) but it's triggering the 25em breakpoint at 900px, which makes no sense at all. So it turns out that yes, there is a bug in Safari, but it's not with how it handles px media queries, it's with how it handles em media queries. When zoomed in, Safari triggers em breakpoints much later than it should. Practical Consequences Imagine a tiled layout where the number of columns changes based on the viewport width. Here's what it might look like at around 960px wide: Here's what happens when I zoom in to 150% in Safari using pixel media queries: The layout drops to two columns, like you'd see at 640px viewport size. Now look what happens when I zoom in to 150% in Safari using em media queries: We're seeing the single column layout, even though the text is rendering at exactly the same size as it was when zoomed in using pixel media queries! Again, this is because Safari is triggering the breakpoint far too late when using em units. For good measure, here's what things look like at 100% and 150% using both px and em units in Safari, Chrome, and Firefox: One of these things is not like the other 🤔 Just Use Pixels Pixels are the only unit that behave consistently across all commonly used browsers. "But what if the user has changed their browser's default font size?" The short answer is set an explicit root font size in pixels. It will override the user's custom default font size which is mildly annoying for the user, but it will force them to zoom instead which always behaves the way you want. You can still get bitten by Safari's "never use font sizes smaller than _" setting if the user has it set to a high value, but that setting has the power the wreak havoc on your designs no matter what you do. I try not to stress about it too much.

CSS Utility Classes and "Separation of Concerns"

Over the last several years, the way I write CSS has transitioned from a very "semantic" approach to something much more like what is often called "functional CSS." Writing CSS this way can evoke a pretty visceral reaction from a lot of developers, so I'd like to explain how I got to this point and share some of the lessons and insights I've picked up along the way. Phase 1: "Semantic" CSS One of the best practices you'll hear about when you're trying to learn how to CSS good is "separation of concerns." The idea is that your HTML should only contain information about your content, and all of your styling decisions should be made in your CSS. Take a look at this HTML: <p class="text-center"> Hello there! </p> See that .text-center class? Centering text is a design decision, so this code violates "separation of concerns" because we've let styling information bleed into our HTML. Instead, the recommended approach is to give your elements class names based on their content, and use those classes as hooks in your CSS to style your markup: <style> .greeting { text-align: center; } </style> <p class="greeting"> Hello there! </p> The quintessential example of this approach has always been CSS Zen Garden; a site designed to show that if you "separate your concerns", you can completely redesign a site just by swapping out the stylesheet. My workflow looked something like this: Write the markup I needed for some new UI (an author bio card in this case): <div> <img src="*o3c1g40EXj65Fq9k." alt=""> <div> <h2>Adam Wathan</h2> <p> Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut. </p> </div> </div> Add a descriptive class or two based on the content: - <div> + <div class="author-bio"> <img src="*o3c1g40EXj65Fq9k." alt=""> <div> <h2>Adam Wathan</h2> <p> Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut. </p> </div> </div> Use those classes as "hooks" in my CSS/Less/Sass to style my new markup: .author-bio { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; > img { display: block; width: 100%; height: auto; } > div { padding: 1rem; > h2 { font-size: 1.25rem; color: rgba(0,0,0,0.8); } > p { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; } } } Here's a demo of the final result: See the Pen "Semantic" mapping layer (terrible idea!) by Adam Wathan (@adamwathan) on CodePen. This approach intuitively made sense to me, and for a while this is how I wrote HTML and CSS. Eventually though, something started to feel a bit off. I had "separated my concerns", but there was still a very obvious coupling between my CSS and my HTML. Most of the time my CSS was like a mirror for my markup; perfectly reflecting my HTML structure with nested CSS selectors. My markup wasn't concerned with styling decisions, but my CSS was very concerned with my markup structure. Maybe my concerns weren't so separated after all. Phase 2: Decoupling styles from structure After looking around for a solution to this coupling, I started finding more and more recommendations towards adding more classes to your markup so you could target them directly; keeping selector specificity low and making your CSS less dependent on your particular DOM structure. The most well-known methodology that advocates this idea is Block Element Modifer, or BEM for short. Taking a BEM-like approach, the markup for our author bio might look more like this: <div class="author-bio"> <img class="author-bio__image" src="*o3c1g40EXj65Fq9k." alt=""> <div class="author-bio__content"> <h2 class="author-bio__name">Adam Wathan</h2> <p class="author-bio__body"> Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut. </p> </div> </div> ...and our CSS would look like this: .author-bio { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .author-bio__image { display: block; width: 100%; height: auto; } .author-bio__content { padding: 1rem; } .author-bio__name { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .author-bio__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; } View on CodePen This felt like a huge improvement to me. My markup was still "semantic" and didn't contain any styling decisions, and now my CSS felt decoupled from my markup structure, with the added bonus of avoiding unnecessary selector specificity. But then I ran into a dilemma. Dealing with similar components Say I needed to add a new feature to the site: displaying a preview of an article in a card layout. Say this article preview card had a full bleed image on the top, a padded content section below, a bold title, and some smaller body text. Say it looked exactly like an author bio. What's the best way to handle this while still separating our concerns? We can't apply our .author-bio classes to our article preview; that wouldn't be semantic. So we definitely need to make .article-preview its own component. Here's what our markup could look like: <div class="article-preview"> <img class="article-preview__image" src="" alt=""> <div class="article-preview__content"> <h2 class="article-preview__title">Stubbing Eloquent Relations for Faster Tests</h2> <p class="article-preview__body"> In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality. </p> </div> </div> But how should we handle the CSS? Option 1: Duplicate the styles One approach would be to straight up duplicate our .author-bio styles and rename the classes: .article-preview { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .article-preview__image { display: block; width: 100%; height: auto; } .article-preview__content { padding: 1rem; } .article-preview__title { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .article-preview__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; } This works but of course it's not very DRY. It also makes it a bit too easy for these components to differ in slightly different ways (maybe a different padding, or font color) which can lead to an inconsistent looking design. Option 2: @extend the author bio component Another approach is to use the @extend feature of your preprocessor of choice; letting you piggy-back off of the styles already defined in our .author-bio component: .article-preview { @extend .author-bio; } .article-preview__image { @extend .author-bio__image; } .article-preview__content { @extend .author-bio__content; } .article-preview__title { @extend .author-bio__name; } .article-preview__body { @extend .author-bio__body; } View on CodePen Using @extend at all is generally not recommended, but that aside, this feels like it solves our problem right? We've removed the duplication in our CSS, and our markup is still free of styling decisions. But let's examine one more option... Option 3: Create a content-agnostic component Our .author-bio and .article-preview components have nothing in common from a "semantic" perspective. One is the bio of an author, the other is a preview of an article. But as we've already seen, they have a lot in common from a design perspective. So if we wanted to, we could create a new component named after what they do have in common, and reuse that component for both types of content. Let's call it a .media-card. Here's the CSS: .media-card { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .media-card__image { display: block; width: 100%; height: auto; } .media-card__content { padding: 1rem; } .media-card__title { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .media-card__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; }'s what the markup for our author bio would look like: <div class="media-card"> <img class="media-card__image" src="*o3c1g40EXj65Fq9k." alt=""> <div class="media-card__content"> <h2 class="media-card__title">Adam Wathan</h2> <p class="media-card__body"> Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut. </p> </div> </div> ...and here's the markup for our article preview: <div class="media-card"> <img class="media-card__image" src="" alt=""> <div class="media-card__content"> <h2 class="media-card__title">Stubbing Eloquent Relations for Faster Tests</h2> <p class="media-card__body"> In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality. </p> </div> </div> This approach also removes the duplication from our CSS, but aren't we "mixing concerns" now? Our markup all of a sudden knows that we want both of these pieces of content to be styled as media cards. What if we wanted to change how the author bio looked without changing how the article preview looks? Before, we could just open up our stylesheet and choose new styles for either of the two components. Now we'd need to edit the HTML! Blasphemy! But let's think about the flip side for a minute. What if we needed to add a new type of content that also needed the same styling? Using a "semantic" approach, we'd need to write the new HTML, add some content-specific classes as styling "hooks", open up our stylesheet, create a new CSS component for the new content type, and apply the shared styles, either through duplication or using @extend or a mixin. Using our content-agnostic .media-card class, all we'd need to write is the new HTML; we wouldn't have to open the stylesheet at all. If we're really "mixing concerns", shouldn't we need to make changes in multiple places? "Separation of concerns" is a straw man When you think about the relationship between HTML and CSS in terms of "separation of concerns", it's very black and white. You either have separation of concerns (good!), or you don't (bad!). This is not the right way to think about HTML and CSS. Instead, think about dependency direction. There are two ways you can write HTML and CSS: ~~"Separation of Concerns"~~ CSS that depends on HTML. Naming your classes based on your content (like .author-bio) treats your HTML as a dependency of your CSS. The HTML is independent; it doesn't care how you make it look, it just exposes hooks like .author-bio that the HTML controls. Your CSS on the other hand is not independent; it needs to know what classes your HTML has decided to expose, and it needs to target those classes to style the HTML. In this model, your HTML is restyleable, but your CSS is not reusable. ~~"Mixing Concerns"~~ HTML that depends on CSS. Naming your classes in a content-agnostic way after the repeating patterns in your UI (like .media-card) treats your CSS as a dependency of your HTML. The CSS is independent; it doesn't care what content it's being applied to, it just exposes a set of building blocks that you can apply to your markup. Your HTML is not independent; it's making use of classes that have been provided by the CSS, and it needs to know what classes exist so that it combine them however it needs to to achieve the desired design. In this model, your CSS is reusable, but your HTML is not restyleable. CSS Zen Garden takes the first approach, while UI frameworks like Bootstrap or Bulma take the second approach. Neither is inherently "wrong"; it's just a decision made based on what's more important to you in a specific context. For the project you're working on, what would be more valuable: restyleable HTML, or reusable CSS? Choosing reusability The turning point for me came when I read Nicolas Gallagher's About HTML semantics and front-end architecture. I won't reiterate all of his points here, but needless to say I came away from that blog post fully convinced that optimizing for reusable CSS was going to be the right choice for the sorts of projects I work on. Phase 3: Content-agnostic CSS components My goal at this point was to explicitly avoid creating classes that were based on my content, instead trying to name everything in a way that was as reusable as possible. That resulted in class names like: .card .btn, .btn--primary, .btn--secondary .badge .card-list, .card-list-item .img--round .modal-form, .modal-form-section ...and so on and so forth. I noticed something else when I started focusing on creating reusable classes: The more a component does, or the more specific a component is, the harder it is to reuse. Here's an intuitive example. Say we were building a form, with a few form sections, and a submit button at the bottom. If we thought of all of the form contents as part of a .stacked-form component, we might give the submit button a class like .stacked-form__button: <form class="stacked-form" action="#"> <div class="stacked-form__section"> <!-- ... --> </div> <div class="stacked-form__section"> <!-- ... --> </div> <div class="stacked-form__section"> <button class="stacked-form__button">Submit</button> </div> </form> But maybe there's another button on our site that's not part of a form that we need to style the same way. Using the .stacked-form__button class on that button wouldn't make a lot of sense; it's not part of a stacked form. Both of these buttons are primary actions on their respective pages though, so what if we named the button based on what the components have in common and called it .btn--primary, removing the .stacked-form__ prefix completely? <form class="stacked-form" action="#"> <!-- ... --> <div class="stacked-form__section"> - <button class="stacked-form__button">Submit</button> + <button class="btn btn--primary">Submit</button> </div> </form> Now say we wanted this stacked form to look like it was in a floated card. One approach would be to create a modifier and apply it to this form: - <form class="stacked-form" action="#"> + <form class="stacked-form stacked-form--card" action="#"> <!-- ... --> </form> But if we already have a .card class, why don't we compose this new UI using our existing card and stacked form? + <div class="card"> <form class="stacked-form" action="#"> <!-- ... --> </form> + </div> By taking this approach, we have a .card that can be a home for any content, and an unopinionated .stacked-form that can be used inside of any container. We're getting more reuse out of our components, and we didn't have to write any new CSS. Composition over subcomponents Say we needed to add another button to the bottom of our stacked form, and we wanted it to be spaced out a little from the existing button: <form class="stacked-form" action="#"> <!-- ... --> <div class="stacked-form__section"> <button class="btn btn--secondary">Cancel</button> <!-- Need some space in here --> <button class="btn btn--primary">Submit</button> </div> </form> One approach would be to create a new subcomponent, like .stacked-form__footer, add an additional class to each button like .stacked-form__footer-item, and use descendant selectors to add some margin: <form class="stacked-form" action="#"> <!-- ... --> - <div class="stacked-form__section"> + <div class="stacked-form__section stacked-form__footer"> - <button class="btn btn--secondary">Cancel</button> - <button class="btn btn--primary">Submit</button> + <button class="stacked-form__footer-item btn btn--secondary">Cancel</button> + <button class="stacked-form__footer-item btn btn--primary">Submit</button> </div> </form> Here's what the CSS might look like: .stacked-form__footer { text-align: right; } .stacked-form__footer-item { margin-right: 1rem; &:last-child { margin-right: 0; } } But what if we had this same problem in a subnav somewhere, or a header? We can't reuse the .stacked-form__footer outside of a .stacked-form, so maybe we make a new subcomponent inside of our header: <header class="header-bar"> <h2 class="header-bar__title">New Product</h2> + <div class="header-bar__actions"> + <button class="header-bar__action btn btn--secondary">Cancel</button> + <button class="header-bar__action btn btn--primary">Save</button> + </div> </header> ...but now we have to duplicate the effort we put into building our .stacked-form__footer in our new .header-bar__actions components. This feels a lot like the problem we ran into way back at the beginning with content-driven class names doesn't it? One way to solve this problem is to come up with an entirely new component that's easier to reuse, and use composition. Maybe we make something like an .actions-list: .actions-list { text-align: right; } .actions-list__item { margin-right: 1rem; &:last-child { margin-right: 0; } } Now we can get rid of the .stacked-form__footer and .header-bar__actions components completely, and instead use an .actions-list in both situations: <!-- Stacked form --> <form class="stacked-form" action="#"> <!-- ... --> <div class="stacked-form__section"> <div class="actions-list"> <button class="actions-list__item btn btn--secondary">Cancel</button> <button class="actions-list__item btn btn--primary">Submit</button> </div> </div> </form> <!-- Header bar --> <header class="header-bar"> <h2 class="header-bar__title">New Product</h2> <div class="actions-list"> <button class="actions-list__item btn btn--secondary">Cancel</button> <button class="actions-list__item btn btn--primary">Save</button> </div> </header> But what if one of these actions lists was supposed to be left justified, and the other was supposed to be right justified? Do we make .actions-list--left and .actions-list--right modifiers? Phase 4: Content-agnostic components + utility classes Trying to come up with these component names all of the time is exhausting. When you make modifiers like .actions-list--left, you're creating a whole new component modifier just to assign a single CSS property. It's already got left in the name, so you're not going to fool anyone that it's "semantic" in any way either. What if we had another component that needed left-align and right-align modifiers, would we create new component modifiers for that as well? This gets back to same problem we were facing when we decided to kill .stacked-form__footer and .header-bar__actions and replace them with a single .actions-list: We prefer composition to duplication. So if we had two actions lists, one that needed to be left aligned and another that needed to be right aligned, how could we solve that problem with composition? Alignment utilities To solve this problem with composition, we need to be able to add a new reusable class to our component that gives us the desired effect. We were already going to call our modifers .actions-list--left and .actions-list--right, so there's no reason not to call these new classes something like .align-left and .align-right: .align-left { text-align: left; } .align-right { text-align: right; } Now we can use composition to make our stacked form buttons left-aligned: <form class="stacked-form" action="#"> <!-- ... --> <div class="stacked-form__section"> <div class="actions-list align-left"> <button class="actions-list__item btn btn--secondary">Cancel</button> <button class="actions-list__item btn btn--primary">Submit</button> </div> </div> </form> ...and our header buttons right-aligned: <header class="header-bar"> <h2 class="header-bar__title">New Product</h2> <div class="actions-list align-right"> <button class="actions-list__item btn btn--secondary">Cancel</button> <button class="actions-list__item btn btn--primary">Save</button> </div> </header> Don't be afraid If seeing the words "left" and "right" in your HTML makes you feel uncomfortable, remember we have been using components named after visual patterns in our UI for ages at this point. There's no pretending that .stacked-form is any more "semantic" than .align-right; they're both named after how they affect the presentation of the markup, and we are using those classes in our markup to achieve a specific presentational result. We're writing CSS-dependent HTML. If we want to change our form from a .stacked-form to a .horizontal-form, we do it in the markup, not the CSS. Deleting useless abstractions The interesting thing about this solution is that our .actions-list component is now basically useless; all it did before was align the contents to the right. Let's delete it: - .actions-list { - text-align: right; - } .actions-list__item { margin-right: 1rem; &:last-child { margin-right: 0; } } But now it's a little weird to have an .actions-list__item without an .actions-list. Is there another way we can solve our original problem without creating an .actions-list__item component? If you think back, the whole reason we created this component was to add a little bit of margin between two buttons. .actions-list was a pretty decent metaphor for a list of buttons because it was generic and fairly reusable, but certainly there could be situations where we need the same amount of spacing between items that aren't "actions" right? Maybe a more reusable name would be something like .spaced-horizontal-list? We already deleted the actual .actions-list component though, because it's only the children that really need any styling. Spacer utilities If only the children need styling, maybe it would be simpler to style the children independently instead of using fancy pseudo-selectors to style them as group? The most reusable way to add some spacing next to an element would be a class that let's us say "this element should have some space next to it". We already added utilities like .align-left and .align-right, what if we made a new utility just for adding some right margin? Let's create a new utility class, something like .mar-r-sm, for adding a small amount of margin to the right of an element: - .actions-list__item { - margin-right: 1rem; - &:last-child { - margin-right: 0; - } - } + .mar-r-sm { + margin-right: 1rem; + } Here's what our form and header would look like now: <!-- Stacked form --> <form class="stacked-form" action="#"> <!-- ... --> <div class="stacked-form__section align-left"> <button class="btn btn--secondary mar-r-sm">Cancel</button> <button class="btn btn--primary">Submit</button> </div> </form> <!-- Header bar --> <header class="header-bar"> <h2 class="header-bar__title">New Product</h2> <div class="align-right"> <button class="btn btn--secondary mar-r-sm">Cancel</button> <button class="btn btn--primary">Save</button> </div> </header> The entire concept of an .actions-list is nowhere to be seen, our CSS is smaller, and our classes are more reusable. Phase 5: Utility-first CSS Once this clicked for me, it wasn't long before I had built out a whole suite of utility classes for common visual tweaks I needed, things like: Text sizes, colors, and weights Border colors, widths, and positions Background colors Flexbox utilities Padding and margin helpers The amazing thing about this is that before you know it, you can build entirely new UI components without writing any new CSS. Take a look at this sort of "product card" component from a project of mine: Here's what my markup looks like: <div class="card rounded shadow"> <a href="..." class="block"> <img class="block fit" src="..."> </a> <div class="py-3 px-4 border-b border-dark-soft flex-spaced flex-y-center"> <div class="text-ellipsis mr-4"> <a href="..." class="text-lg text-medium"> Test-Driven Laravel </a> </div> <a href="..." class="link-softer"> @icon('link') </a> </div> <div class="flex text-lg text-dark"> <div class="py-2 px-4 border-r border-dark-soft"> @icon('currency-dollar', 'icon-sm text-dark-softest mr-4') <span>$3,475</span> </div> <div class="py-2 px-4"> @icon('user', 'icon-sm text-dark-softest mr-4') <span>25</span> </div> </div> </div> The number of classes used here might make you balk at first, but say we did want to make this a real CSS component instead of composing it out of utilities. What would we call it? We don't want to use content-specific names because then our component could only be used in one context. Maybe something like this? .image-card-with-a-full-width-section-and-a-split-section { ... } Of course not, that's ridiculous. Instead we'd probably want to compose it out of smaller components, like we've talked about before. What might those components be? Well maybe it's housed in a card. Not all cards have a shadow though so we could have a .card--shadowed modifier, or we could create a .shadow utility that could be applied to any element. That sounds more reusable, so let's do that. It turns out some of the cards on our site don't have rounded corners, but this one does. We could make it .card--rounded, but we have other elements on the site that are sometimes rounded the same amount too, and those aren't cards. A rounded utility would be more reusable. What about the image at the top? Maybe that's something like a .img--fitted, so it fills the card? Well there's a few other spots on the site where we need to fit something to it's parent width, and it's not always an image. Maybe just a .fit helper would be better. can see where I'm going with this. If you follow that trail far enough with a focus on reusability, building this component out of reusable utilities is the natural destination. Enforced consistency One of the biggest benefits of using small, composable utilities is that every developer on your team is always choosing values from a fixed set of options. How many times have you needed to style some HTML and thought, "this text needs to be a little darker," then reached for the darken() function to tweak some base $text-color? Or maybe, "this font should be a little smaller," and added font-size: .85em to the component you're working on? It feels like you're doing things "right", because you're using a relative color or a relative font size, not just arbitrary values. But what if you decide to darken the text by 10% for your component, and someone else darkens it by 12% for their component? Before you know it you end up with 402 unique text colors in your stylesheet. This happens in every codebase where the way you style something is to write new CSS: GitLab: 402 text colors, 239 background colors, 59 font sizes Buffer: 124 text colors, 86 background colors, 54 font sizes HelpScout: 198 text colors, 133 background colors, 67 font sizes Gumroad: 91 text colors, 28 background colors, 48 font sizes Stripe: 189 text colors, 90 background colors, 35 font sizes GitHub: 163 text colors, 147 background colors, 56 font sizes ConvertKit: 128 text colors, 124 background colors, 70 font sizes This is because every new chunk of CSS you write is a blank canvas; there's nothing stopping you from using whatever values you want. You could try and enforce consistency through variables or mixins, but every line of new CSS is still an opportunity for new complexity; adding more CSS will never make your CSS simpler. If instead, the solution to styling something is to apply existing classes, all of a sudden that blank canvas problem goes away. Want to mute some dark text a little? Add the .text-dark-soft class. Need to make the font size a little smaller? Use the .text-sm class. When everyone on a project is choosing their styles from a curated set of limited options, your CSS stops growing linearly with your project size, and you get consistency for free. You should still create components One of the areas where my opinion differs a bit from some of the really die-hard functional CSS advocates is that I don't think you should build things out of utilities only. If you look at some of the popular utility-based frameworks like Tachyons (which is a fantastic project), you'll see they create even button styles out of pure utilities: <button class="f6 br3 ph3 pv2 white bg-purple hover-bg-light-purple"> Button Text </button> Whoa. Let me break this one down: f6: Use the sixth font size in the font size scale (.875rem in Tachyons) br3: Use the third border radius in the radius scale (.5rem) ph3: Use the third size in the padding scale for horizontal padding (1rem) pv2: Use the second size in the padding scale for vertical padding (.5rem) white: Use white text bg-purple: Use a purple background hover-bg-light-purple: Use a light purple background on hover If you need multiple buttons that have this same combination of classes, the recommended approach with Tachyons is to create an abstraction through templating rather than through CSS. If you were using Vue.js for example, you might create a component that you would use like this: <ui-button color="purple">Save</ui-button> ...and be defined something like this: <template> <button class="f6 br3 ph3 pv2" :class="colorClasses"> <slot></slot> </button> </template> <script> export default { props: ['color'], computed: { colorClasses() { return { purple: 'white bg-purple hover-bg-light-purple', lightGray: 'mid-gray bg-light-gray hover-bg-light-silver', // ... }[this.color] } } } </script> This is a great approach for a lot of projects, but I still think there are a lot of use cases where it's more practical to create a CSS component than it is to create a template-based component. For the sort of projects I work on, it's usually simpler to create a new .btn-purple class that bundles up those 7 utilities than it is to commit to templatizing every tiny widget on the site. ...but build them using utilities first The reason I call the approach I take to CSS utility-first is because I try to build everything I can out of utilities, and only extract repeating patterns as they emerge. If you're using Less as your preprocessor, you can use existing classes as mixins. That means that creating this .btn-purple component takes only a bit of multi-cursor wizardry in your editor: Unfortunately you can't do this in Sass or Stylus without creating a separate mixin for every utility class, so it's a bit more work there. It's not always possible for every single declaration in a component to come from a utility of course. Complex interactions between elements like changing a child's property when hovering over a parent are hard to do with utilities-only, so use your best judgment and do whatever feels simpler. No more premature abstraction Taking a component-first approach to CSS means you create components for things even if they will never get reused. This premature abstraction is the source of a lot of bloat and complexity in stylesheets. Take a navbar for example. How many times in your app do you rewrite the markup for your main nav? In my projects I typically only do that once; in my main layout file. If you build things with utilities first and only extract components when you see worrisome duplication, you probably never need to extract a navbar component. Instead, your navbar might look something like this: <nav class="bg-brand py-4 flex-spaced"> <div><!-- Logo goes here --></div> <div> <!-- Menu items go here --> </div> </nav> There's just nothing there worth extracting. Isn't this just inline styles? It's easy to look at this approach and think it's just like throwing style tags on your HTML elements and adding whatever properties you need, but in my experience it's very different. With inline styles, there are no constraints on what values you choose. One tag could be font-size: 14px, another could be font-size: 13px, another could be font-size: .9em, and another could be font-size: .85rem. It's the same blank canvas problem you face when writing new CSS for every new component. Utilities force you to choose: Is this text-sm or text-xs? Should I use py-3 or py-4? Do I want text-dark-soft or text-dark-faint? You can't just pick any value want; you have to choose from a curated list. Instead of 380 text colors, you end up with 10 or 12. My experience is that building things utility-first leads to more consistent looking designs than working component-first, as unintuitive as it might sound at first. Where to start If this approach sounds interesting to you, here's a few frameworks worth checking out: Tachyons Basscss Beard turretcss Recently, I also released my own free open-source PostCSS framework called Tailwind CSS that's designed around this idea of working utility-first and extracting components from repeated patterns: If you're interested in checking it out, head over to the Tailwind CSS website and give it a try.

The $61,392 Book Launch That Let Me Quit My Job

Last May I released my first digital product, a book called "Refactoring to Collections" that teaches PHP developers how to use functional programming ideas to write cleaner code. Within the first three days it generated $61,392 in revenue and gave me the confidence to quit my day job and work on my own ideas full-time. Here's the whole story, from idea to launch. Finding an Idea Feb 16, 2016 – The Conversation That Got Things Started I've always loved the feeling of creating something neat and polished and putting it out into the world; whether that be video game walkthroughs with ASCII art titles when I was 8 years old, or sophisticated training spreadsheets when I was competing in powerlifting. One of the spreadsheets I spent too much time on at the peak of my powerlifting career. So when I started getting more passionate about programming, writing a book was something that really excited me. But after a bunch of false starts, it started to feel like I was never going to follow through; it just seemed too ambitious. One day I noticed that a friend of mine in a different industry had recently transitioned to creating books and courses full-time, so I asked him out for coffee to find out how it was working out for him. I explained to him that I had this idea for a book on automated testing for PHP developers, and that I kept failing to do anything with it because I was intimidated by the scope of the project. "Have you thought of doing a tripwire product first?" I'd never even heard of a tripwire product. "It's basically just something small that you can sell for $9 or $10." Interesting! "It would help you get some experience launching something and make a bigger project feel a lot more doable." This made a ton of sense to me. So what could I put together in a week or two? Feb 21, 2016 – Pitching the Idea About a year before I started working on the book, I published a screencast called "Refactoring Loops and Conditionals". I'd gotten really great feedback on it and I'd been using tons of related ideas to simplify my code for a while at that point, so I thought hey, maybe I could put together a little mini-book with some other tips and tricks on refactoring using collections? I pitched it to some friends to see what they thought: And so it was decided, I was going to work on a little tips and tricks guide to refactoring with collections. Pre-Launch Strategy Feb 23, 2016 – Announcing the Book I put together a simple landing page to collect emails (I use ConvertKit, it's great) and tweeted that I was working on the book: I'm writing a short book on refactoring with collections! Check it out if you're sick of ifs, loops, and variables. — Adam Wathan (@adamwathan) February 23, 2016 I also sent an email to the small list that I'd built up from my blog (about 400 people) letting them know about the book and where to sign up if they were interested in updates. Mar 21, 2016 – First Update Email "Refactoring to Collections preview!" – 713 recipients (view email) About a month after announcing the book, I sent out the first chapter sample to my list. Leading up to this, I shared a lot of what I was working on via Twitter and was pretty happy to have ~700 people to send the sample to. I started getting lots of amazing feedback right away which was super encouraging: Wow, this looks fantastic! Reminds me of Clean Code but dig that the samples are in PHP. Also, feels like good prep for teaching devs to think. I'll be buying this! A few people even asked if they could pre-order! Mar 29, 2016 – Another Sample Chapter + Announcing a Launch Date "Another Refactoring to Collections sample!" – 903 recipients (view email) About a week later I sent out another sample chapter, and announced my planned launch date: Monday April 11th, 2016. I jumped the gun a bit on this (which I'll talk more about later), but it certainly did light a fire under my ass to crank on finishing the book. Apr 7, 2016 – Releasing a Chapter on My Blog "Using transpose to clean up form input" – 1373 recipients (view email) A few days before my planned launch date, I took one of the chapters from the book and adapted it as a blog post, hoping to help drive more mailing list sign ups so I had a bigger audience to launch to. I sent this email to my whole list, not just the people who had signed up for book updates. Apr 10, 2016 – Changing the Launch Date "Refactoring to Collections launch update" – 1072 recipients (view email) I'd tried my best to finish this thing by my original launch date, but the further I got with it the more I realized it wasn't going to be just a 30-40 page mini-book anymore. Here's the email I sent out: Hey! A few weeks ago I announced that I was planning to release the book on April 11, only a day away! The bad news is that it turns out getting a book ready for release is a lot more work than I expected, and even though I've been cranking hard on it every spare moment, it's still not quite going to be ready in time to release tomorrow :( The good news is that the biggest reason for the delay is that I just have way more important content to share than I originally expected. The book has turned into something a lot more awesome than I ever could've dreamed, and I'm going to be so proud to share it with you when it's finally ready. A couple things might change based on any feedback I get from friends reviewing the book for me, but check out this table of contents: (view in original email) I'm also working on covering the examples from the book in a series of screencasts, as well as preparing an extensive set of exercises in the form of unit tests that you can use to really get some of these patterns and ideas under your fingers. This thing is really close to being ready and I promise it's gonna be worth the wait. Thanks for your patience! I was super nervous sending this. I felt like I was really letting everyone down by not delivering what I promised, so I was really surprised and relieved to get responses like this: Wow, the table of content looks amazing! I am really looking forward to this book. Great to hear that there will be exercises and screencasts, this will make a big difference. Take your time, I'm sure it will be worth it! Apr 26, 2016 – New Sample Chapter + Progress Update "Book update + another free chapter!" – 1212 recipients (view email) It had been about two weeks since I sent out any emails (although I'd still been sharing things on Twitter) so I sent out another free chapter sample. I didn't have a new launch date picked yet, but I let everyone know what I had left to finish, and that I was expecting it to clock in at around 180 pages. May 16, 2016 – Announcing the Launch Details "Refactoring to Collections launch details + free screencast!" – 1459 recipients (view email) I finally had all of the content ready, so I sent out a massive update which included a free screencast, the complete table of contents, all of the package/pricing information, and the new launch date: Wednesday May 18th, 2016, two days away. I spent a lot of time crafting this email so I recommend reading the entire thing, but here's the package and pricing information I decided on: The Bare Essentials – ~~$39~~ $29 The 150-page book, in PDF and HTML formats The comprehensive set of unit test exercises The Premium Training Package – ~~$79~~ $59 The 150-page book, in PDF and HTML formats The comprehensive set of unit test exercises 4 hours of screencasts, covering all of the book examples + 3 advanced bonus tutorials The Complete Reference Package – ~~$179~~ $135 The 150-page book, in PDF and HTML formats The comprehensive set of unit test exercises All of the screencasts The source code to Nitpick CI, a complete production-ready Laravel SaaS app that makes heavy use of collection pipelines The Launch May 18, 2016 – Launch Day "Refactoring to Collections is now available!" – 1506 recipients (view email) I was up until 2:00am designing and building the sales page, which I quietly put live in place of the pre-launch page before trying (and failing) to get some sleep. I don't know if this happens to other people, but I've never had a worse case of imposter syndrome in my entire life than I did the night before launching the book. "This whole thing is terrible, no one is going to want this!" "Why am I pretending I'm an author?! What was I thinking!?" "People are going to buy it and hate it and want their money back and I'm going to look like a complete idiot!" I was this close to deleting everything, but I was lucky enough to have a friend to talk to the whole night that helped me realize how ridiculous I was being. To my surprise I had 14 sales by the time I got up around 6:00am, even though I hadn't told a soul that the book was actually available for sale yet! This was pretty exciting, but I still had no idea what to expect when I actually made the announcement. At 7:12am (probably too early, I was excited :P) I sent out a short email announcing that the book was available. Then I sat in bed with my laptop; fingers crossed that anyone would actually buy. Expectations Going into the launch, I was going to be really happy if I did $5,000 on the first day, and $20,000 over the lifetime of the book. I knew of about a dozen people who had been replying to my emails that I was sure would buy, but beyond that I had no idea what to expect. By 7:30am (18 minutes after I sent the announcement email), the book had sold 33 copies and made $2,179. By 8:00am: 62 copies and $4,076. By 8:30am: 81 copies and $5,243. This was my email inbox that morning: It was the craziest feeling I've ever experienced. Announcing on Twitter I waited as long as I could to make sure people were actually online before I sent out the tweet, but 8:44am was the longest I could hold out. I sent out this tweet about 10 minutes earlier, letting people know I was about to announce the launch and that I'd love if they would share: About to officially announce the Refactoring to Collections launch! Would love any help sharing it! 🤗❤️ — Adam Wathan (@adamwathan) May 18, 2016 Then I finally announced the launch itself: 🎉 The "Refactoring to Collections" book + video course is finally available! Grab it on launch day for 25% off! — Adam Wathan (@adamwathan) May 18, 2016 I can't say how much it helped for sure, but I think actually asking people to share beforehand really made a difference. (I picked that one up from @wesbos!) Hourly Sales Here's what sales looked like on the first day, hour by hour: Revenue by hour on launch day By the end of the first day, the book had made $28,299. I was blown away. May 19, 2016 – Second Day of Launch Sale "Refactoring with Collections and Null Objects, free screencast" – 1803 recipients (view email) On the second day of the launch, I sent another email to anyone on the list who hadn't purchased yet (ConvertKit makes this really easy.) I included four testimonials from early customers, like this one: Couldn't put @adamwathan's "Refactoring to Collections" down. Less than 24 hours later I'm already done. Can't wait to share with my team. — philwinkle (@philwinkle) May 19, 2016 I also posted one of the screencasts on my blog for free, and linked to it in the email. I did another $14,614 in sales this day, bringing the total up to $42,913. May 20, 2016 – Final Day of Launch Sale "Last day to get 25% off Refactoring to Collections" – 1796 recipients (view email) I sent out one last email letting people know the sale was ending, and included a few more testimonials. I also included this excerpt from the closing chapter in the book: It's hard for me to put in to words, but there's something beautiful about taking some data, piping it through a series of discrete transformations, and having the solution come out on the other side. There's something clean and pure about it that makes it an extremely seductive style of programming. To this day, every time I encounter a new problem the first thing I think is "how can I solve this with a collection pipeline?", and I am continually amazed by just how often I can use this approach to find an elegant solution. By the end of the day, the book had made another $18,479, finishing out the launch with **$61,392** in total revenue. Revenue across entire launch sale, colored by day What I Think Worked Starting Small I'd wanted to create some sort of digital product for years leading up to this but never followed through because every idea I had was too ambitious or intimidating. Once I convinced myself I was just putting together a 30-40 page mini-book, getting started and making progress became a whole lot easier. Sure, it turned out a lot bigger than I planned, but if I knew that from day one, I never would've mustered up the motivation to actually write the damn thing. Start small! Making Promises I find making progress is a lot easier when I feel like I'm letting someone down by not delivering what I promised. For this book, that started with putting up a landing page and collecting emails. I'd promise a sample chapter on Twitter before it was finished, just to make sure I'd get it done: 🕵 Sending out another book preview tomorrow, diving deep into `reduce`! Sign up if you'd like to check it out: — Adam Wathan (@adamwathan) March 28, 2016 When those techniques got me far enough along, I promised a launch date, and busted my ass every night trying to hit it. If you have a hard time with distractions or procrastination, set yourself up for embarrassment if you don't get things done. It definitely works for me! Sharing Progress Instead of just working on the book in private, I did my best to share everything I could possibly think to share. If I came up with a great way to phrase an idea, I'd share it on Twitter: Some more book progress, refactoring through positive thinking! — Adam Wathan (@adamwathan) March 19, 2016 When I finished an example I was really excited about, I'd give it away to the mailing list for free: Just finished a screencast? I'd tweet out my to-do list so people could see where things were at: Three to go! These last ones monsters, wish me luck. — Adam Wathan (@adamwathan) May 15, 2016 This really helped people get excited about the book, and the feedback I got kept me motivated to keep working. Doubling Revenue with Tiered Pricing There's a limit to what people will spend on a PDF. Even though I truly believe the ideas in Refactoring to Collections are worth the $179 price tag of the top tier, books as a category have an established market rate. No matter how much time it saves someone nor how much effort it took to write, it's almost impossible to get someone to spend more than about $50 on a digital book, even when they believe it's worth the price. I can't even convince myself to spend $179 on a book! So if you want to make a full time living from digital products, you need to help people justify paying what your product is really worth. The easiest way to do that is to try and take your product out of the "book" category. Add screencasts, sample projects, or exercises; or include access to a forum, or a 30-minute coaching call. Make it easy to evaluate your product as its own unique offering, not just another eBook. Here's what the revenue breakdown looked like between my tiers at the end of the 3-day launch sale: Bare Essentials Package (just the book, $29) Premium Training Package (book + screencasts, $59) Complete Reference Package (book + screencasts + sample project, $135) That's an average selling price of $67.84; more than double the price of just the book. Writing the book took me about 9 weeks, the screencasts about one week, and the sample project was something I already had lying around. If I didn't let anyone pay more than $29, I would've only made $26,245 from the same number of customers. That's less than half the revenue for a lot more than half the work. Closing Thoughts I launched Refactoring to Collections to a list of 1500 people and about 4000 Twitter followers. That's not a tiny audience, but it's a lot less than I thought I'd need to have such a successful launch. Two weeks after releasing the book, I decided to give my notice at my day job and now I get to work full-time on the other product ideas I'd always been too intimidated to start (like Test-Driven Laravel, which is an even crazier launch story I'll save for another day.) It took just under 3 months for the book to cross $100,000, and now 11 months after launch it's made a total of **$138,835.** It still brings in between $1000-$2000 each month. When I was working on the book, launch stories from people like Nathan Barry and Sean McCabe really made a difference in keeping me motivated and pushing me to the finish line, so I hope this post can do the same for someone else. If you've always wanted to create something but felt too intimidated to follow through, hopefully there's a few things in here that make launching your own product seem more achievable. If you're working on something and have any questions you think I could help with, I'm @adamwathan on Twitter!