Oops, something went wrong! Site admins have been notified.

In Part 2: Recipes 101 + Branding we turned the stock theme into the Gig City brand — colors, fonts, logo. Now we build the pieces that make the homepage ours: the night-sky hero over the Tennessee River, the giant "25 Gbps" billboard, a set of hand-drawn Chattanooga icons, and the mountain-ridgeline footer. Every one of them is a component, and Drupal has a tidy, version-controllable way to write them.

Canvas is a module, not a theme

The first thing to get straight: Canvas (the page builder powering our Byte-based site) is a Drupal module (drupal:canvas), not a theme. It powers component-based page building. Pages on this site aren't rendered by old-school field formatters and "Manage Display" screens — they're assembled out of components arranged on a Canvas page. So if we want a custom hero, we don't hack a template buried in the theme; we write a real, reusable component and drop it onto the page.

The primary kind of component for Byte is a Single-Directory Component, or SDC. (Canvas also supports "Code Components" written in Preact/JSX, but those are incompatible with Tailwind-based themes like Byte — so everything we build here is SDC.)

The three-file SDC pattern

An SDC is exactly what the name says: one folder per component, holding everything that component needs. Byte ships about 30 of them under themes/byte_theme/components/, and Canvas auto-discovers any new folder there on a cache rebuild. Each of our custom components is three files:

  • name.component.yml — the schema. It declares the component's props (inputs) with names, types, defaults, and example values.
  • name.twig — the markup. A Twig template that renders the props into HTML (and, in our case, a lot of inline SVG).
  • name.tailwind.css — the styling. Tailwind/CSS scoped to that component, imported into the theme's CSS bundle.

Here's the real schema for our hero, themes/byte_theme/components/cd-hero/cd-hero.component.yml — trimmed to show the shape:

name: CD Hero
group: Chattanooga.Digital
status: stable
props:
  type: object
  required:
    - headline
    - lede
  properties:
    headline:
      type: string
      title: Headline (HTML allowed)
      default: "Take control of your tech. Go <em>open</em>!"
      examples:
        - "Take control of your tech. Go <em>open</em>!"

One non-obvious rule bit us early and is worth memorizing: every required prop must have an examples: value, not just a default:. Canvas runs each component through a strict validator before it will register it, and a required prop without an example is silently rejected — nothing shows up in the page builder and there's no obvious error. The schema-level default: is honored at render time but does not satisfy the validator. So: examples on everything required.

Building the hero

The hero (cd-hero) renders the "night sky over the Tennessee River" treatment: a dark gradient sky, the Walnut Street Bridge silhouette across the lower third, Lookout Mountain behind it, and animated fiber-light packets traveling along the bridge deck. The fun part is that the animation is pure SVG — no JavaScript, no framework. The packets are <circle> elements with SVG <animate> children:

<circle cx="-100" cy="510" r="4">
  <animate attributeName="cx" from="-100" to="1540"
           dur="6s" repeatCount="indefinite"/>
</circle>

Four packets travel left-to-right at staggered begin offsets so the bridge always looks "lit." A prefers-reduced-motion: reduce media query in the component's CSS halts the motion for visitors who ask for that. To create a component from scratch you scaffold the folder, add the three files, and wire its CSS into the bundle with a single import in themes/byte_theme/src/main.css:

mkdir -p themes/byte_theme/components/cd-hero
@import "../components/cd-hero/cd-hero.tailwind.css";

Then rebuild the Tailwind bundle and the cache so Canvas discovers the new component:

make theme-build
make cr

The stat billboard

Next comes cd-stat-billboard — an editorial-scale number sandwiched between the hero and the first content section. It renders the Gig City "25 Gbps" claim at roughly 360px in the Fraunces serif, with three flanking statistic columns (9,000 mi · 2010 · −55%) below. It's the same three-file pattern: a .component.yml with 14 props (the headline number, unit, eyebrow, claim, plus three sets of statN_num / statN_unit / statN_label), a .twig using a <sup> for the unit, and a .tailwind.css with the big Fraunces typography rules and a responsive three-column grid.

This is the component where the "examples on every required prop" rule earns its keep. If the billboard ever fails to appear after a rebuild, the first thing to check is Canvas's rejection reasons — it tells you exactly which prop is missing an example.

A custom Chattanooga icon set

Generic tech glyphs would have been faster, but the design calls for icons that reference real Chattanooga things. We started with eight hand-drawn SVGs (the set has since grown to thirteen as new sections needed glyphs), all stored under themes/byte_theme/icons/cd/:

  • walnut-street-truss.svg — the 1890 pedestrian bridge silhouette
  • fiber-optic-cross-section.svg — an EPB fiber cable feeding a terminal prompt
  • moccasin-bend.svg — the Tennessee River U-loop seen from Lookout Mountain
  • lookout-mountain.svg, open-fiber.svg, handshake-bridge.svg, signal-from-lookout.svg, and smart-grid-switch.svg for the early sections
  • plus later additions — drupal-drop.svg, cloud-self-hosted.svg, grid-node.svg, penguin-circuit.svg, and podium-bridge.svg — as the middle sections came together

Every one shares the same conventions: a 64×64 viewBox, a 1.6px stroke with square joins for an engineering feel, and — crucially — stroke="currentColor". That last detail means the SVGs inherit their color from CSS, so we can re-tint them per context without editing the file. The icons are consumed by a component called cd-feature-grid, whose Twig loads each one inline:

{{ source('@byte_theme/icons/cd/' ~ icon ~ '.svg') }}

Loading the SVG inline (rather than as an <img>) is what lets currentColor work — the icon becomes part of the page's DOM and picks up the Fiber Blue from its surrounding style. The grid then arranges three cards in an asymmetric "data flowing down the bridge" cascade, lifting and adding a teal halo on hover.

The mountain-ridgeline footer

The last piece is the footer, and it's the exception that proves the rule: it's the only one of these that is not a new component. The footer markup belongs to Byte's own footer SDC, and changing it would either break Byte's "don't subtheme" guidance or affect every other Byte-based site. So the footer is pure CSS, living in one file — themes/byte_theme/src/cd-footer-skin.css — wired in with the same one-line import:

@import "./cd-footer-skin.css";

It hooks into the .region-footer wrapper class Byte already emits and uses pseudo-elements to stamp the Lookout Mountain ridgeline silhouette across the top edge (an inline data:image/svg+xml background — no extra HTTP request) and a "Built on the Gig City" stamp at the bottom. CSS-only, no Twig override, no recipe change. It's the smallest piece in the whole build.

What's next

That's the visible surface of the site — hero, billboard, icons, footer — all built as small, version-controlled components that make rebuild reproduces exactly. But a homepage is the easy part. In Part 4 we go beneath the design and build the thing Drupal is genuinely great at: the data model — the content types and typed fields that turn this from a brochure into a real application.

← Part 2: Recipes 101 + Branding  ·  Part 4: The Data Model →

More insights

Want updates from Chattanooga.Digital?

Pre-join the co-op to receive new posts, workshop schedules, and member updates.