Why Goose?
Goose is a website starter for the modern web. Its goal is simple; provide a solid foundation for a fast and adaptable website that’s enjoyable to build, and easy to maintain.
It combines a trio of fast, flexible tools (11ty, Lightning CSS, and Esbuild) with a simple, micro-management-free CSS architecture, and lightweight patterns that set you up for success. Highlights:
- Designed for in-browser design. Fast tools and a straightforward custom-property-powered composable CSS architecture makes designing in browser a joy, and scaffolding new pages fast.
- Expressive color. Goose includes a comprehensive color system that lets you easily support light & dark mode, custom themes, composable themes, and all of the above.
- Fluid type. A straightforward fluid type system makes it easy to keep your type game on point across a wide range of devices and screen sizes.
- Flexible bundling. Import everything into monolithic CSS and JS bundles? Break out page and section bundles? Inline critical CSS? All supported out of the box, zero configuration.
Getting started
- Clone the Goose repository on GitHub.
- Install with
npm install. - To start the local dev server, run
npm start. - Set your site’s name, description, URL, language, author, social image, and theme colors in
_source/_data/site.json. - Build your homepage in
_source/index.html. The blog, pricing, an about page, and more are optional features infeatures/: add them one at a time, likenpm run add:blog, or pick withnpm run add. This manual is part of every site; delete_source/manual/if yours doesn’t need it. - Once the blog is added, write posts as Markdown files in
_source/posts/_content/. They’re listed at/blog/and published in the Atom feed at/feed.xml. - Goose deploys to Cloudflare Workers as a static site. Set your Worker name and custom domain in
wrangler.toml, connect the repository with Workers Builds, and usenpm run buildas the build command. To deploy somewhere else, runnpm run publish:prodand serve the_publicfolder.
Working with CSS
Goose uses a practical, declarative approach to writing modern CSS, using three breakpoints (small, medium, large), and three cascade layers:
baseis where you define the default presentation characteristics for your project. Tailoring the custom media queries, custom properties, typeface, and base elements alone should get you halfway there. A kitchen sink demo is included to make it easy to see the impact of your changes across a wide variety of elements.componentsis where you define commonly repeated objects that live across your project. Components care little about their position on any given page.utilitiesis for CSS that manipulates elements and components. Goose aims to cover the small subset of attributes that are highly variable (page position, type characteristics, spacing) as opposed to making every property available. This approaches delivers the key benefits of utility css (breaking the linear ratio between page count and lines of CSS, less repetition) without the tooling complexity of build-dependent approaches. Utility names follow Tailwind’s conventions (mt-sm,text-lg,flex-col) on top of Goose’s own spacing, type, and color scales. Responsive variants keep Goose’s suffix:text-lg@mdapplies from medium screens up, andsr-only@smon small screens only.
Syntax, draft specs
Components in Goose use a modified BEM syntax, which uses a terse block --modifier1 --modifier2 syntax instead of block--modifier1 block--modifier2.
Lightning is configured to transpile draft custom media syntax, but nothing else. Adjust as need be for the project at hand.
Breaking up CSS
Goose gives you three options for including CSS:
- In the main bundle. Global stuff goes here.
- As route-specific bundles. Add a CSS file to
source/css/pages(or any folder not prefixed by an underscore) to create a new entrypoint. Assign the path tostylesheetin template frontmatter to use it. - As
styletags. For critical CSS or page-specific one-offs, Goose includes an 11ty bundle that processes CSS with Lightning and adds it to a style tag in the page head.
Working with JS
Like with CSS, Goose supports config-free javascript bundles. A main bundle is included by default, and you can add JS files to source/js (or any sub-folder not prefixed by an underscore) to create a new bundles. Assign paths to javascript in template frontmatter to use them.
Goose is designed to use the web platform directly, and assumes ESM. A few small-but-handy custom elements are included; an element that makes anchored popovers menus gracefully degrade in unsupported browsers, an element that makes details only act as such on mobile, and an element that generates randomly positioned shapes that you can style.
Layout
Layout with Goose is handled primarily by grid and flex utilties. Both are opt-in, which makes it easy to mix in more specific layout needs (or wholesale replace with a different approach). Use margin and padding utilities to finnesse interplay between elements.
Grid
Rapidly compose responsive layouts with an infinitely nestable 12 column+2 “bleed” column grid that collapses to 1 column +2 bleed columns on small screens. Add the .grid class to a parent container to opt in.
<section class="grid">
<div class="start-1 span-6"></div>
<div class="start-auto bleed-end"></div>
</section>
<section class="grid">
<div class="span-1/3"></div>
<div class="span-1/3"></div>
<div class="span-1/3"></div>
</section>
Grid children
.start-{1-12/auto}sets the column start position from medium screens up.span-{1-12}sets how many columns the element spans from medium screens up.bleed,.bleed-{start/end}makes elements stick to page edges.place-{start/center/end}set placement of content within a grid
Flex
Add the .flex or .inline-flex class to a parent container to opt in.
.gap-{sm/md/lg}sets row and column gap.gap-x-{sm/md/lg}sets column gap.items-{start/center/end}sets alignment.justify-{start/center/between/end}sets justification.flex-nowrapprevents items from wrapping.flex-colswitches to a vertical orientation- Append
@mdto apply at medium+, or@smfor small screens only
Margin
.mt-{0/xs-xxl/flex}sets block start margin.mb-{0/xs-xxl/flex}sets block end margin.ms-{0/xs-xxl/flex}sets inline start margin.me-{0/xs-xxl/flex}sets inline end margin- Prepend
-to set negative margin, as in.-mt-lg - Append
@mdto apply at medium+, as in.-mt-lg@md
Padding
.pt-{0/xs-xxl/flex}sets block start padding.pb-{0/xs-xxl/flex}sets block end padding.ps-{0/xs-xxl/flex}sets inline start padding.pe-{0/xs-xxl/flex}sets inline end padding- Append
@mdto apply at medium+
Typography
Goose uses a semi-fluid type system that attempts to do most of the heavy-lifting for you. Set --font-size-base, --font-size-scale-sm, and --font-size-scale-md in @root.css and Goose takes it from there. Line height defaults to --line-height-fluid, which decreases as type size increases. A secondary step-based system steps in when you need fine-grained control.
Typographic utilities
.text-{xxxs-xxxxl}adjusts font size.font-{xs-xxl}adjusts font weight.max-w-{xs-xl}limits width to a maximum number of characters.leading-{none/xxs-lg}adjusts line height.tracking-{sm-xl}adjusts letter spacing.text-balanceand.text-prettybalance all lines and the last line of text{.text-start/.text-center/.text-end}changes text alignment.italic,.uppercase,.list-none,.no-underlinedo what they suggest- Append
@mdto apply at medium+, as in.text-lg@md
Color & opacity
Goose includes a fully featured color system built around modern features like relative color, light-dark(), and color-mix() that lets you implement page themes, component themes, and light/dark mode without having to micro-manage color. Here’s how it works:
- Set your key colors, like
--primary,--secondary,--neutral, and--highlight(the<mark>yellow, used by the--color-markpreset) in@root.css. These colors, along with--50to--900tint & shade presets form your base color system. - Create color “presets” like
--color-text,--color-bg, etc with baked in light and dark support, and use them liberally to reduce micro-management and make theme-friendly components. To define presets, use relative color syntax along with light-dark(), like so:
--color-text: light-dark(
oklch(from var(--primary) var(--700)),
oklch(from var(--primary) var(--300) / 50%)
)
- Make component-level exceptions by using the same strategy above. Remember that you can always use
calc()expressions instead of tint & shade presents, like so:
border-color: light-dark(
oklch(from var(--secondary) calc(l * 1.5) c h),
oklch(from var(--secondary) calc(l / 1.5) c h)
)
- Use color and opacity utilities to make context-specific adjustments and one-offs.
- Create additional themes by creating a class in
css/_base/themes.cssthat redefines your base colors and your color presets. Add the class to the HTML element using thethemefront matter key for a page-level theme, or to elements for component-level theming. You can also use.lightor.darkto force light or dark mode at the page or component level.
Color & opacity utilities
.colordoes nothing on it’s own; use with modifiers below.--{text/bg/border/accent}sets a remap target.--use-{text/bg/border/accent}remaps to a different part’s color.set-{text/bg}sets color.text-opacity-{10-90}adjusts text opacity.bg-opacity-{10-90}adjusts background opacity.border-opacity-{10-90}adjusts border opacity
Animation
Goose includes a simple, tiny, composable animation system based on scroll-triggered and scroll-linked CSS animation:
- Use
animate-self,animate-children, andanimate-scrollto trigger animations as elements enter and exit the viewport. The animation is set tofade-inby default. - Change animations by creating and applying new animation utilities. See
.fade-infor reference. - Use the
delayanddurationutilities to further refine your animations.
Blocks
Blocks are ready-made sections for marketing pages, in _source/_includes/blocks/. Each one reads its settings from a variable named after it, which you set in a page’s front matter (or with {% set %}), then you include the block where it should appear:
{% include "blocks/showcase.html" %}
A block’s CSS is added only to the pages that use it, inline in the page’s <style>, so a site with many blocks doesn’t send all of their styles everywhere. See them live in the kitchen sink.
Showcase
A list of features beside a picture of the open one. On small screens it’s an accordion, with each picture under its item. It works without JavaScript: the items are <details> elements with a shared name, so the browser keeps one open at a time.
showcase:
autoplay: 6 # optional: seconds per item
ratio: 4 / 3 # optional: the pictures' aspect ratio (4 / 3 by default)
items:
- title: Plan your day
text: Schedule tasks, reminders and habits.
image: showcase/plan.png # a path under assets/images/
alt: A to-do list for today
With autoplay, a timer bar beside the open item fills in the accent color, and the next item opens when it’s full, looping back to the first. It’s built to stay out of the way:
- Hovering the items, or focusing one with the keyboard, holds the timer.
- A Pause button holds it until the visitor presses Play.
- Scrolling the showcase off screen holds it until it’s back.
- Clicking an item stops it for good: the visitor has chosen what to read.
- It never plays for people who have turned on reduced motion.
Change the timer bar’s thickness with --showcase-bar in showcase.css. To show two showcases on one page, give each an id, since that becomes the name that groups its items.
Call to action
A signup panel for the end of a page, with a button to site.signupUrl from site.json. Every setting is optional:
cta:
source: pricing # adds ?type_source=pricing to the signup link, to track where signups come from
title: Ready to start?
text: Create your account in minutes.
button: Get started
{% include "blocks/cta.html" %}
FAQ
Questions that open to show their answers. The questions are templates with a title and the answer as their content, like the pricing feature’s pricing/questions/NN-slug.md, sorted by file name. Without questions, the block renders nothing.
{% set faq = { title: "Questions?", questions: collections.faq } %}
{% include "blocks/faq.html" %}
Adding a block
A block is a template in _includes/blocks/, with its CSS beside it. The template adds that CSS to the page with {% css %}{% include "blocks/<name>.css" %}{% endcss %}, and the CSS goes in @layer components. Behavior, like the showcase’s autoplay, belongs in a custom element in assets/js/_elements/, and the block must still work without it.
One catch: a block inside another template’s content, like a kitchen-sink snippet or a blog post shown on a listing, adds its CSS to that template instead of the page showing it. Load the block’s CSS on that page, the way kitchen-sink.html does.