Manual

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

  1. Clone the Goose repository on GitHub.
  2. Install with npm install.
  3. To start the local dev server, run npm start.
  4. Set your site’s name, description, URL, language, author, social image, and theme colors in _source/_data/site.json.
  5. Build your homepage in _source/index.html. The blog, pricing, an about page, and more are optional features in features/: add them one at a time, like npm run add:blog, or pick with npm run add. This manual is part of every site; delete _source/manual/ if yours doesn’t need it.
  6. 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.
  7. 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 use npm run build as the build command. To deploy somewhere else, run npm run publish:prod and serve the _public folder.

Working with CSS

Goose uses a practical, declarative approach to writing modern CSS, using three breakpoints (small, medium, large), and three cascade layers:

  1. base is 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.
  2. components is where you define commonly repeated objects that live across your project. Components care little about their position on any given page.
  3. utilities is 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@md applies from medium screens up, and sr-only@sm on 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:

  1. In the main bundle. Global stuff goes here.
  2. 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 to stylesheet in template frontmatter to use it.
  3. As style tags. 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-nowrap prevents items from wrapping
  • .flex-col switches to a vertical orientation
  • Append @md to apply at medium+, or @sm for 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 @md to 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 @md to 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-balance and .text-pretty balance all lines and the last line of text
  • {.text-start/.text-center/.text-end} changes text alignment
  • .italic, .uppercase, .list-none, .no-underline do what they suggest
  • Append @md to 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:

  1. Set your key colors, like --primary, --secondary, --neutral, and --highlight (the <mark> yellow, used by the --color-mark preset) in @root.css. These colors, along with --50 to --900 tint & shade presets form your base color system.
  2. 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%)
)
  1. 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)
)
  1. Use color and opacity utilities to make context-specific adjustments and one-offs.
  2. Create additional themes by creating a class in css/_base/themes.css that redefines your base colors and your color presets. Add the class to the HTML element using the theme front matter key for a page-level theme, or to elements for component-level theming. You can also use .light or .dark to force light or dark mode at the page or component level.

Color & opacity utilities

  • .color does 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:

  1. Use animate-self, animate-children, and animate-scroll to trigger animations as elements enter and exit the viewport. The animation is set to fade-in by default.
  2. Change animations by creating and applying new animation utilities. See .fade-in for reference.
  3. Use the delay and duration utilities 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.