Layouts are used for "wrapping" the content of individual pages with common elements, such as the
<head></head>
and footer sections, which usually contain necessities such as<link>
and<script>
tags.
A very simple layout might look something like this:
<!DOCTYPE html>
<html lang ="en" >
<head >
<meta charset ="UTF-8" >
<title > {{title }} </title >
</head >
<body >
{{> body }}
</body >
</html >
Where {{> body }}
is the point of insertion for content from any pages that use this layout.
In the Assemble task options you can either specify a general layout to be used for all pages in a project:
assemble: {
options: {
layout: 'layouts/default.hbs'
}
}
When your project has different types or groupings of pages, such as "docs", "blog" and "examples" for instance, and there are certain design or structural elements that are unique to each grouping, it often makes sense to:
blog
target might have a right sidebar, whereas every page in the docs
target
might have a left sideabar:For example:
assemble: {
options: {
layout: 'default.hbs',
layoutdir: 'layouts'
},
docs: {
// override task-level layout
options: {layout: 'docs-layout.hbs' },
files: {'docs/': ['src/docs/*.hbs' ]},
},
site: {
// override task-level layout
options: {layout: 'site-layout.hbs' },
files: {'site/': ['src/site/*.hbs' ]},
}
// ... other targets
}
Layouts can also be "stacked" so that the parent-most layouts contain the most commonly used elements while "child layouts" contain elements that are only necessary for specific pages or groupings of pages.
If, for example:
<script></script>
and <link>
tags,This is a good opportunity to use nested layouts that might look something like this:
Our parent layout will only contain the most commonly required, generalized elements. We'll call this layout parent-layout.hbs
:
<!DOCTYPE html>
<html lang ="en" >
<head >
<meta charset ="UTF-8" >
<title > {{title }} </title >
<link rel ="stylesheet" href ="main.css" >
</head >
<body >
{{> body }}
<script src ="main.js" > </script >
</body >
</html >
Our "child" layout will contain additional markup for our sidebar, but it should also inherit all of the markup from the parent layout (since layouts are "flattened" before pages are rendered). For the markup to be inherited, we need to specify a parent layout for our child layout.
Specifying a layout for a layout works the exact same way as specifying a layout for any reguar page. It can be defined in the options.layout
property in the Assemble task config in the Gruntfile (at the task and/or target
level), or the layout can be specified in the child layout's YAML front matter. Here is an example of the latter:
child-layout.hbs
---
layout: parent-layout.hbs
---
<!-- Only the child layout should have this sidebar! -->
<div class="row">
<div class="col-lg-3">
<div class="sidebar">
<a href="#">Buttons</a>
<ul class="nav">
<li><a href="#button-expand-left"></a>Expand Left</li>
<li><a href="#button-expand-right"></a>Expand Right</li>
<li><a href="#button-expand-up"></a>Expand Up</li>
<li><a href="#button-expand-down"></a>Expand Down</li>
</ul>
</div>
</div>
<div class="col-lg-9">
<!-- render content from pages that specify this layout -->
{{> body }}
</div>
</div>
You might recall that the {{> body }}
tag is the "insertion point" in a layout, specifying exactly where the content from each page will be passed in. Layouts are no different. A child layout will be inserted (flattened)
into the parent layout at the point of the {{> body }}
tag.
Continuing with the example from the previous section, the flattened layout for pages that use the "child" layout might look something like this during build:
<!DOCTYPE html>
<html lang ="en" >
<head >
<meta charset ="UTF-8" >
<title > {{title }} </title >
<link rel ="stylesheet" href ="main.css" >
</head >
<body >
<div class ="row" >
<div class ="col-lg-3" >
<div class ="sidebar" >
<a href ="#" > Buttons</a >
<ul class ="nav" >
<li > <a href ="#button-expand-left" > </a > Expand Left</li >
<li > <a href ="#button-expand-right" > </a > Expand Right</li >
<li > <a href ="#button-expand-up" > </a > Expand Up</li >
<li > <a href ="#button-expand-down" > </a > Expand Down</li >
</ul >
</div >
</div >
<div class ="col-lg-9" >
{{> body }}
</div >
</div >
<script src ="main.js" > </script >
</body >
</html >
data.layout
property for pages defined in an options.pages
collection.Layout Specificity
As with CSS, the more specific layout wins:
Level | Description | |
---|---|---|
none | Layouts are optional. | |
Task | Defined at the task-level of the assemble task. | Project level |
Target | Defined at the target-level in the assemble task. | Sub-project level |
Layout property (data) | Defined in the data: {} object in a pages collection. |
Page and/or sub-page level |
Layout property (YFM) | Defined in the YAML front matter of a page. | Page and/or sub-page level |
Layouts are optional, but the {{> body }}
tag is required for content to be pulled into a layout.
<!DOCTYPE html>
<html >
<head >
<title > {{title }} </title >
</head >
<body >
<!-- the body "pulls in" content from pages -->
{{> body }}
</body >
</html >
options
for each target. Offers greater control over which "types" of pages get which layout
.Since you can create as many
targets as you require, defining layouts in the Gruntfile is a great way of quickly setting up your layout "defaults". In the assemble
task in your Gruntfile.js, you can define a layout at the task-level, and/or a
different layout for each build target:
assemble: {
options: {
layout: 'layouts/default.hbs'
},
site: {
files: {
'site/': ['templates/pages/*.hbs' ]
}
},
blog: {
options: {
layout: 'layout/post.hbs'
},
files: {
'blog/': ['templates/posts/*.hbs' ]
}
},
docs: {
options: {
layout: 'layouts/docs.hbs'
},
files: {
'docs/': ['templates/docs/*.hbs' ]
}
}
}
If you require a higher level of granularity than defining layouts in the Gruntfile, you may also define layouts on a page-by-page basis, thus overriding both the task-level ("global") defaults and the target-level defaults.
To do so, just add the layout to the YFM of the page like this:
---
layout: path/to/layout.hbs
---
layout: false
or layout: none
to the YAML front matter of any page that should build without a layout.layout: false
or layout: none
to the options of any target that should build pages without a layout.Your imagination is the only limit to what can be done with layouts, so these are just examples.
<!DOCTYPE html>
<html >
<head >
<title > {{title }} </title >
</head >
<body >
<!-- HEADER -->
<header class ="masthead subhead" >
<div class ="container" >
<div class ="row" >
<div class ="col col-lg-12" >
<h1 > <span class ="text-muted" > Docs / </span > {{#if section }} {{ section }} {{else }} {{basename }} {{/if }} </h1 >
</div >
</div >
</div >
</header >
<!-- CONTENT -->
<div class ="container" >
{{> body }}
</div >
<!-- FOOTER -->
{{> footer }}
</body >
</html >
Layouts can be nested inside other layouts. This enables highly granular control over how pages and layouts are organized. A common strategy is to use a basic "master" or "default" layout for a site, which contains only the most common, bare necessities for a site. This default layout is then inherited by other more specialized layouts.
For example, your default layout may contain the <head></head>
, footer, common link and script tags for a site:
<!DOCTYPE html>
<html lang ="en" >
<head >
{{> header }}
</head >
<body >
{{> body }}
<section id ="footer" >
{{> footer }}
</section >
</body >
</html >
And more specialize layouts might contain side navigation for a section of the site, like this:
---
layout: default.hbs
---
<header class ="masthead subhead" >
<div class ="container" >
<h1 > <span class ="text-muted" > Docs / </span > {{titleize basename }} </h1 >
</div >
</header >
<ul class ="nav nav-pills nav-stacked" >
{{#each pages }}
<li > <a href ="{{relative .. /page.dest this.dest }} " > {{data.title }} </a > </li >
{{/each }}
</ul >
{{> body }}
At build time layouts are "flattened" first before pages are passed in. In other words, the above example becomes this first:
<!DOCTYPE html>
<html lang ="en" >
<head >
{{> header }}
</head >
<body >
<header class ="masthead subhead" >
<div class ="container" >
<h1 > <span class ="text-muted" > Docs / </span > {{titleize basename }} </h1 >
</div >
</header >
<ul class ="nav nav-pills nav-stacked" >
{{#each pages }}
<li > <a href ="{{relative .. /page.dest this.dest }} " > {{data.title }} </a > </li >
{{/each }}
</ul >
{{> body }}
<section id ="footer" >
{{> footer }}
</section >
</body >
</html >
Layouts don't have to be HTML, they can be whatever language you want them to be. If you're generating markdown pages instead of HTML, maybe for a project wiki or other markdown documentation, then you'll want to use a markdown
formatted layout
.
In this example, instead of adding adding the traditional head and footer we'll add some link references to make sure the same ones are used on every generated page. This makes it easier to maintain links and it also cuts down on potential for broken links:
{{> body }}
* * *
_This page was generated using Grunt and [assemble][repo] on {{ today }}._
[org]: https://github.com/assemble
[repo]: https://github.com/assemble/assemble
[issues]: https://github.com/assemble/assemble/issues
[docs]: https://github.com/assemble/assemble-docs
# {{pkg.name}} {{travis-badge}}
> {{pkg.description}}
{{> body }}
* * *
_This readme was generated using Grunt and [assemble][repo] on {{ today }}._
[org]: https://github.com/assemble
[repo]: https://github.com/assemble/assemble
[issues]: https://github.com/assemble/assemble/issues
[docs]: https://github.com/assemble/assemble-docs
{{> body }}
tag to render content from any file that uses the layout.See the template for this page →
Find an error? Let us know →