All chapters
  1. 1 Before you start
  2. 2 Install
  3. 3 Pages
  4. 4 Your first template
  5. 5 Components
  6. 6 Site frame
  7. 7 Page fields
  8. 8 Page lists
  9. 9 Custom settings
  10. 10 Measuring visit
  11. 11 Live parts
  12. 12 Go live
  13. 13 What’s next

The frame: one layout for every template

Soon there will be several templates. So that each one doesn’t repeat the header, footer and styles, we’ll gather them into a frame, and templates will put only their own part inside it.

Chapter 6 of 13 · 3 min read

What repeats

Look at Base. It has the tab title, the header, the footer and the page styles. The kitchen template and the projects template will need all of that too. Only the middle differs: a kitchen has a photo and a price, and projects have a list of work.

So we’ll move everything they share into a frame component. The frame draws the header and footer, and in the middle it puts whatever the template passed to it.

The Layout component

Create a component named Layout:

The Layout component
<!-- @component
Site frame: tab title, header, footer and shared styles. The template puts its own part inside.
-->
<script>
  import Footer from '$components/Footer';
  import Header from '$components/Header';
  import { site } from '$core';

  let { page, children } = $props();
</script>

<svelte:head>
  <title>{page.longTitle || page.title} — {site().name}</title>
</svelte:head>

<Header />

<main>
  {@render children()}
</main>

<Footer note="kitchens made to your measurements" />

<style>
  :global(body) {
    margin: 0;
    background: #f1efeb;
    color: #1f1e1c;
    font: 18px/1.6 system-ui, sans-serif;
  }

  :global(h1) {
    font-size: 2.75rem;
    line-height: 1.05;
    letter-spacing: -0.02em;
  }

  :global(img) {
    max-width: 100%;
    height: auto;
  }

  main {
    max-width: 48rem;
    margin: 0 auto;
    padding: 0 1.5rem 2rem;
  }
</style>

Two things matter here.

  • let { page, children } = $props();: the frame receives the page (for the tab title) and children, which is everything the template puts between the frame’s opening and closing tags.
  • {@render children()}: “draw here what was passed inside”. This is exactly where the middle of each template ends up.

The styles for headings and images sit inside :global(…), so they apply to the whole page. Without it, the frame’s styles would only affect its own markup.

Base inside the frame

Replace the code of Base:

The Base template
<script>
  import Content from '$core/Content';
  import Layout from '$components/Layout';

  let { page } = $props();
</script>

<Layout {page}>
  <h1>{page.longTitle || page.title}</h1>
  <Content of={page} />
</Layout>

Eight lines instead of fifty. <Layout {page}> is short for <Layout page={page}>: when the parameter has the same name as the value, you don’t have to repeat it.

The site looks exactly as it did at the end of the last chapter. That’s intended: we only changed how the site is built. In return, the next template will take a couple of minutes.