The AMP Project is a newly deployed open source initiative backed by Google, with the goal of helping make sites faster, particularly those with heavy use of media and/or need for monetization. It’s essentially a “no fuss” way of rolling out what could otherwise be somewhat complex optimization methods, with most of the processes taken care of behind the scenes for you.
In our article AMP Project: Will it Make Your Sites Faster?, we get into detail on what AMP will mean to you from a developer point of view, and give you some raw numbers on what kind of speed gains AMP may or may not give you.
In this tutorial we’ll be more practical, with a step by step guide for actually creating an AMP page from scratch, explaining how to do some common tasks the AMP way, and sharing some tips as we go.
Let’s begin!
Add the Boilerplate Code
Adding standard boilerplate code is where every AMP template starts. There is a set of code that must be present in every AMP template, or it will fail validation.
Create a new HTML file named index.html and add the following code to it:
<!doctype html><html amp lang="en"><head><meta charset="utf-8"><title>Make an AMP Page</title><link rel="canonical" href="/index.html"><meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"><style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript><script async src="https://cdn.ampproject.org/v0.js"></script></head><body><h1>Make an AMP Page</h1></body></html>
You can read a breakdown of each element of the above code at:
Setup a localhost Preview
You can (if you choose to) preview your newly created AMP template by opening it up directly in a browser. However it’s always a good idea to preview on a localhost instead. This approach simulates a web host, so the environment you are previewing your template in is as close as possible to the live environment it will be deployed to.
For this purpose, I recommend using MAMP for Windows or Mac. If you don’t already have MAMP installed, head to the MAMP website and download a copy.
Once you have MAMP installed, find its htdocs folder. On Mac you will typically find it at /Applications/MAMP/htdocs, and on Windows at C:\MAMP\htdocs\.
Inside it, create a new folder to house your project, e.g. myproject. Then move the index.html file you created in the previous step into the folder.
Run MAMP and you should now be able to preview your AMP template by going to the URL http://localhost:8888/myproject/ in Chrome. Even if you prefer another browser, please use Chrome as we’ll be using Chrome Developer Tools in the next step.
Your AMP page should currently look like this:

If you go to this URL and it doesn’t work, it’s possible MAMP may be running on a different port number to the 8888 you see in the URL provided above. To find out if this is the case, on the MAMP interface click the button that reads Open WebStart page. This will take you to a page with information on MAMP, and in the URL you’ll see the correct port number to use instead:

Turn on Debug
Before we go any further, we want to turn on AMP’s debug mode so if we accidentally do something that doesn’t pass validation we’ll know about it right away.
To do this, add #development=1
to the end of your preview URL, e.g. http://localhost:8888/myproject/#development=1.
Then open up Chrome Developer Tools and go to the Console tab, where you should see the following Powered by AMP ⚡ HTML... message appear:

As you develop your page, if anything doesn’t pass AMP validation you’ll see an error message in the console. Right now we have no error messages and instead see “AMP validation successful”, letting us know everything is working as it should be.
Optional JSON Metadata
Along with the boilerplate code you already added, you have the option to add some JSON metadata to the head section in Schema.org format like so:
<script type="application/ld+json"> { "@context": "http://schema.org", "@type": "NewsArticle", "headline": "Open-source framework for publishing content", "datePublished": "2015-10-07T12:02:41Z", "image": [ "logo.jpg" ] }</script>
This is not essential to pass AMP validation, but it is essential in order to be picked up by various places such as Google Search News.
To read more about this metadata visit:
Dealing With Inline CSS
One of the requirements of AMP is that all custom CSS should be placed inline in the head
section, between <style amp-custom>...</style>
tags.
Despite this requirement, actually writing your CSS straight into the head section is not an ideal workflow. What you really need is to be able to write your CSS in an external stylesheet, as you normally would, then have that CSS moved into the appropriate place in the head
sections of all your site’s template files.
We won’t be covering the step by step of how to do this here, but if you would like to know how to use an external stylesheet and still pass AMP validation, please follow the steps in our quick tip tutorial Make AMP's Inline CSS Easier via Jade or PHP before proceeding.
Adding Images
Now that you have your essential code in place, development mode activated, and (optionally) external stylesheet management ready to go, we can now move on to adding some media to your page. We’ll start with the most common type of media: images.
In AMP you don’t use the <img>
tag to load images. Instead you use the custom element <amp-img>
as it will handle loading optimization across all the images in your page.
From here on in the tutorial, we’re going to be working with an existing template design to help speed things along. Go ahead now and grab yourself a copy of the free Ceevee template by StyleShout. This template includes some images you can use to practice embedding into your AMP document.
In the Ceevee templates images folder you'’ll see a large image of sand dunes at night. We’re going to start by placing this into your template. Copy the images folder and paste it into your project folder.
Then, add the following code to embed the image, above the h1
tags that are already in the HTML:
<amp-img src="images/header-background.jpg" width="2264" height="1504" layout="responsive" class="doc_header_bg"></amp-img>
Any form of media placed into an AMP page must have an initial width
and height
set, so unlike using an img
tag, in an amp-img
tag you cannot leave these attributes out.
If you’re not sure exactly what size an image will be it’s okay, as long as the values you enter accurately represent the aspect ratio. The width
and height
values can be considered as placeholders, as AMP can layout the page with the values you provide and then make adjustments after the image is fully loaded.
The layout
attribute is what allows AMP adjust the image’s display after it’s loaded. We have set its value to responsive
, meaning that it the image will maintain the aspect ratio derived from the width and height values, but shrink or expand to fill its container. This is why even if you don’t know an image’s exact size, as long as you have the aspect ratio you’re good to go.
Save and refresh your preview and you should see the image filling up your viewport:

Read more about amp-img
and the layout
attribute at:
Approximating “Cover” Background Images
A common element in web design today is the use of a background image that fills the height and width of the viewport, creating something of an entry presentation for a page. Typically this is done using a background image with the CSS background-size: cover;
applied to it. However in AMP sites we don’t really want to bring in large background images via CSS as this would bypass the system’s loading optimization.
Now, you could certainly make the argument that loading a large background images isn’t ideal for boosting load speed, and for that reason you may be better off leaving large images out all together.
However, if you unavoidably need to create a design with a large, full viewport image you can still at least tap into AMP’s loading prioritization by adding it via the amp-img
tag instead of as a background. Here’s how you can do so, approximating the background “cover” technique in the process.
We’re going to take the image that you just embedded and turn it into a cover image. First, wrap the image with a div
tag using the class doc_header
so you have:
<div class="doc_header"><amp-img src="images/header-background.jpg" width="2264" height="1504" layout="responsive" class="doc_header_bg"></amp-img></div>
Then, if you haven’t already, add some <style amp-custom>...</style>
tags to the head section so we can include some custom CSS. In between those tags add:
.doc_header { height: 100vh; background-color: #000; overflow: hidden; position: relative; }
Usually, instead of what you see above, when creating a cover image a designer will set the height
of the html
and body
elements to 100%
and then also set their cover section to a height
of 100%
, making it fill the viewport.
However this approach will not work with AMP as the CSS it loads enforces the style height: auto!important;
on the body
element, preventing a setting of height: 100%;
from working.
So instead, if you really need something to be the height of the viewport you can use height: 100vh;
, as we’ve done with our doc_header
class. The vh
unit represents “viewport height”, and a value of 100vh
equates to 100% of the viewport height.
If you save and refresh your site now you should see that the “doc_header” div fills the viewport exactly.
At the moment though, the image doesn’t necessarily fill the “doc_header” div. If the viewport is taller than it is wide, there will be a blank black area below it.

To fix this add the class doc_header_bg
to your amp-img
tag like this:
<amp-img src="images/header-background.jpg" width="2264" height="1504" layout="responsive" class="doc_header_bg"></amp-img>
Then add to your CSS:
.doc_header_bg { min-height: 100vh; }
This code will ensure that the image height is never any less than that of the viewport.

Unfortunately, it will still get squashed at narrower widths so it is not as elegant in appearance as using a background image. However, it is as close an approximation as we can get whilst still using the amp-img
element.
Using Web Fonts
Now we’re going to add some custom web fonts into our template. When doing this with AMP you have three loading methods that will pass validation:
- Google Fonts via the origin https://fonts.googleapis.com
- Fonts.com via the origin https://fast.fonts.net
- For everything else:
@font-face
Adding a Google Font
Loading a Google Font should be done via a link element in the head, using the URL provided to you for the font(s) you want at https://www.google.com/fonts.
For our template we’ll be using Open Sans and Libre Baskerville.

To load these two fonts add this code to your head section:
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700,400italic|Libre+Baskerville" rel="stylesheet" type="text/css">
We’re now going to add some text to our site cover area, to which we’ll apply our new web fonts. Inside the existing “doc_header” div, under the image, add a new div with the class doc_header_inner
:
<div class="doc_header_inner"></div>
Inside that div add the following code:
<h1 class="doc_header_title">Simulate Background Image Cover</h1><p class="doc_header_text">This is a basic page created to show you how to work with the <a href="https://ampproject.org/">AMP Project</a>. In this cover section the background image uses AMP loading optimization. The social icons below use AMP compliant custom font loading. Read on to see more ways to use AMP.</p><hr>
Now add the following to your custom CSS:
body { font-family: 'Open Sans', sans-serif; font-size: 1rem; line-height: 2; color: #838C95; } .doc_header_inner { position: absolute; width: 85vw; max-width: 64rem; top: 50%; left: 50%; transform: translate(-50%, -50%); padding-bottom: 2rem; text-align: center; } h1.doc_header_title { font: bold 5.625rem/1.1em 'Open Sans', sans-serif; color: #fff; letter-spacing: -0.125rem; margin: 0 auto 1rem auto; text-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, .8); } @media (max-width: 35rem){ h1.doc_header_title { font-size: 12vw; } } p.doc_header_text { font-family: 'Libre Baskerville'; line-height: 1.9; color: #A8A8A8; margin: 0 auto; width: 70%; text-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, .5); } .doc_header_text span, .doc_header_text a { color: #fff; } .doc_header_inner hr { width: 60%; margin: 1.125rem auto 1.5rem auto; border-color: #2F2D2E; border-color: rgba(150, 150, 150, .1); }
The code above is just regular CSS for the purposes of laying out our newly added text, however you’ll notice that the process of applying our Google Fonts in an AMP template is no different than usual. You use the font-family
property with the value of your chosen font, e.g. font-family: 'Libre Baskerville';
On saving and refreshing your site you should see both Google Fonts now applied to the text in the cover section:

Adding a Custom Font
Next up we’re going to add a custom font that isn’t available from either Google Fonts or Fonts.com, and that’s Font Awesome. Often, if you’re using Font Awesome you might load it up via MaxCDN, however AMP won’t allow this as the CDN is not one of the two approved font origins. As such, we’re going to handle the loading ourself via @font-face
.
Download the Font Files
In order to load the font yourself you’ll first need to download the font files, which you can do at https://fortawesome.github.io/Font-Awesome/. Note you only need “Font Awesome”, not the extra “Fort Awesome” tools that are offered during download.
Once you have the Font Awesome zip file downloaded, extract it and copy the fonts folder it contains into your project folder.
To your CSS, add the following code:
/* Font Awesome */ @font-face { font-family: 'FontAwesome'; src: url('fonts/fontawesome-webfont.eot?v=4.0.3'); src: url('fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } .fa { display: inline-block; font-family: FontAwesome; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .fa-facebook:before { content: "\f09a"; } .fa-twitter:before { content: "\f099"; } .fa-google-plus:before { content: "\f0d5"; } .fa-linkedin:before { content: "\f0e1"; } .fa-instagram:before { content: "\f16d"; } .fa-dribbble:before { content: "\f17d"; } .fa-skype:before { content: "\f17e"; } /* /Font Awesome */
I recommend inserting the above code at the top of your custom CSS to help keep things organized. This code loads in our custom font files and creates classes that can be used to add font icons to a design–it’s the standard CSS provided for use by Font Awesome.
Then also add this CSS:
.doc_header_social { margin: 1.5rem 0; padding: 0; font-size: 1.875rem; text-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, .8); } .doc_header_social li { display: inline-block; margin: 0 1rem; padding: 0; } .doc_header_social li a { color: #fff; } .doc_header_social li a:hover { color: #11ABB0; }
This extra CSS adds some styling specific to our template that we’ll use to setup a row of linked social icons.
Add this HTML after the <hr>
inside the “doc_header_inner” div:
<ul class="doc_header_social"><li><a href="#"><i class="fa fa-facebook"></i></a></li><li><a href="#"><i class="fa fa-twitter"></i></a></li><li><a href="#"><i class="fa fa-google-plus"></i></a></li><li><a href="#"><i class="fa fa-linkedin"></i></a></li><li><a href="#"><i class="fa fa-instagram"></i></a></li><li><a href="#"><i class="fa fa-dribbble"></i></a></li><li><a href="#"><i class="fa fa-skype"></i></a></li></ul>
If you refresh your site now you should see a row of @font-face
powered social icons along the bottom of the text we added earlier:

Integrate the amp-font
Element
To help ensure visitors don’t end up seeing a broken site if custom fonts don’t load successfully, AMP provides the amp-font
element to allow you to create fallbacks.
To make this element work the first thing you’ll need to do is load the AMP script that enables it. In your head section, add this code:
<script async custom-element="amp-font" src="https://cdn.ampproject.org/v0/amp-font-0.1.js"></script>
Now with the amp-font
element ready for use, we can have it add a class to the html
or body
tag if our font fails to load. Add this code to the bottom of your template, before the closing </body>
tag:
<amp-font layout="nodisplay" font-family="FontAwesome" timeout="3000" on-error-add-class="font-awesome-missing"></amp-font>
Let’s take a quick look at the attributes set in this element. We first set layout="nodisplay"
which ensures the element is invisible.
Next, the attribute font-family
is set to FontAwesome
, which tells AMP we want to track the loading of the font named “FontAwesome”. Make sure this value exactly matches the font-family
setting in your @font-face
CSS code.
The attribute timeout
is set to 3000
, which means we’ll allow up to three seconds for the font to load before moving to a fallback. You can change this to whatever value you prefer.
Finally, we set on-error-add-class
to font-awesome-missing
. If the font fails to load after three seconds this class will be added to the html
or body
element. We can target this class to create our fallback behavior by adding this code to our custom CSS:
.font-awesome-missing .doc_header_social { display:none; }
If the font fails load after three seconds the above CSS will activate and hide the entire social icon bar so we don’t have any broken looking display.
Now try temporarily renaming your project’s fonts folder so the fonts fail to load, then refresh your page and you should see the social icons area become hidden. You should also see the class font-awesome-missing
added to the html
or body
tag. Restore the fonts folder to the correct name and you should see your font icons again on refresh.
Read more about the amp-font
element at:
Add a YouTube Video
Next up, we’re going to learn how to add a YouTube video the AMP way, but first we need to add a little housekeeping code to create a section for the video to sit in.
After the “doc_header” div, replace the existing h1
tags with this HTML:
<div class="video_wrap"><div class="standard_width standard_padding"><h2>Embed a YouTube Video</h2></div></div>
Then add this to your custom CSS:
.standard_width { width: 100%; max-width: 75rem; margin-left: auto; margin-right: auto; } .standard_padding { box-sizing: border-box; padding: 6rem 3rem; } .video_wrap { background-color: #2B2B2B; }
Tip: Dealing with the Box Model in AMP Pages
You might have noticed in the above CSS we set box-sizing: border-box;
in the standard_padding
class.
The reason for this is in AMP you can’t use the common technique of including * {box-sizing: border-box}
, because the *
selector is deemed too resource hungry. As such you’ll need to set box-sizing: border-box;
for the specific elements you need it on, or just handle padding and borders the old fashioned way–with a calculator or counting on your fingers.
Integrate the amp-youtube
Element
Now that the section is set up and ready for content, we’re going to add a YouTube video using AMP’s custom amp-youtube
element. By using this element instead of standard YouTube embed code we can tap into loading optimization techniques similar to those of amp-img
.
To begin with, you’ll need to add the AMP JavaScript required to enable the new element. In the head section, paste in:
<script async custom-element="amp-youtube" src="https://cdn.ampproject.org/v0/amp-youtube-0.1.js"></script>
Now add the following HTML inside the divs you just added, under the h2
tags:
<amp-youtube data-videoid="mGENRKrdoGY" layout="responsive" width="600" height="270"></amp-youtube>
To specify the video you want to load, set its YouTube ID on the attribute data-videoid
. Other than this attribute, the amp-youtube
element is much the same as the amp-img
element.
We have the width
and height
set to 600
and 270
respectively. Just as with images, videos must have an initial width and height set.
We then use layout="responsive"
so that the video will fit the size of its container, while keeping the aspect ratio drawn from its height and width settings.
Save and refresh your site, and try resizing the viewport. You should see that your YouTube video expands or shrinks to fit, keeping the correct aspect ratio as it goes.

Read more about the amp-youtube
element at:
Adding iframe Based Content
AMP has additional custom elements for loading content from specific sites, like amp-twitter
, amp-instagram
and a few more. For content from third party sites that don’t have their own dedicated AMP element, there’s the amp-iframe
element instead. We’re now going to use this element to load a Google Map into our page.
Create a container for the map by adding this code below your “video_wrap” divs:
<div class="standard_width standard_padding"><h2>Use iframe Embedding, e.g. Maps</h2></div>
Now, as with the amp-font
and amp-youtube
elements, we’ll need to load the AMP script that drives the amp-iframe
element. Add this to your head section:
<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>
Next, inside your new div and under the h2
tags, add this code:
<amp-iframe width="1100" height="600" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" layout="responsive" frameborder="0" src="https://www.google.com/maps/embed/v1/place?key=AIzaSyDG9YXIhKBhqclZizcSzJ0ROiE0qgVfwzI&q=Alameda,%20CA"></amp-iframe>
Reload your site and you should see a Google Map now in place.
As well as optimizing loading, using the amp-iframe
element helps to ensure that the content being viewed through the iframe doesn’t behave in unwanted ways, such as executing JavaScript that might cause slow loading or forcing popup ads. The sandbox
attribute allows you determine what level of control you apply over iframe content.
For full details on amp-iframe
and its “sandbox” controls, visit:
Setup an Image Gallery with Lightbox
One of the things that can feel limiting when working with AMP is the rule that no custom JavaScript is allowed. On the flip side though, there are custom elements included in AMP that aim to provide you with some of the functionality you might usually implement via custom JavaScript.
For example, instead of loading a custom lightbox script you can use the amp-image-lightbox
element. We’re going to create an image gallery that uses this element now.
Start by creating a container for your gallery by adding this code right above your amp-font
tag:
<div class="portfolio_wrap"><div class="standard_width"><div class="portfolio_inner clearfix"><h2>Create a Gallery with Lightboxes</h2></div></div></div>
Also add this to your custom CSS:
.portfolio_wrap { background-color: #ebeeee; } .portfolio_inner { padding: 4rem 6rem; } .portfolio_item { box-sizing: border-box; float: left; width: 25%; text-align: center; padding: 0.8rem; } .clearfix:before, .clearfix:after { content: " "; display: table; } .clearfix:after { clear: both; }
Like our last few custom elements, we’ll need to load an AMP script to enable the amp-image-lightbox
element. Add this to your head section:
<script async custom-element="amp-image-lightbox" src="https://cdn.ampproject.org/v0/amp-image-lightbox-0.1.js"></script>
Now we’re ready to start setting up our lightbox gallery. Start by adding this code inside your new divs, under the h2
tags:
<amp-image-lightbox id="lightbox1" layout="nodisplay"></amp-image-lightbox>
This amp-image-lightbox
element will create the actual lightbox display with the enlarged images inside it. We want to hide it until a user has clicked an image they want to enlarge, so we set it to layout="nodisplay"
.
Note: we have set the ID of this element to lightbox1
.
To add an item to the gallery, add this div below the amp-image-lightbox
element:
<div class="portfolio_item"></div>
Then inside it add this amp-img
code:
<amp-img on="tap:lightbox1" role="button" tabindex="0" src="images/portfolio/coffee.jpg" width="215" height="215" layout="responsive"></amp-img>
For the most part, this amp-image
element is the same as the one we added earlier, however you’ll notice the addition of on="tap:lightbox1"
. This tells AMP that when the image is tapped/clicked, the larger version should be opened up in a lightbox with the id lightbox1
, i.e. the amp-image-lightbox
element we just created.
Add another seven images to the gallery with this code:
<div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="1" src="images/portfolio/console.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="2" src="images/portfolio/farmerboy.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="3" src="images/portfolio/girl.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="4" src="images/portfolio/into-the-light.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="5" src="images/portfolio/judah.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="6" src="images/portfolio/origami.jpg" width="215" height="215" layout="responsive"></amp-img></div><div class="portfolio_item"><amp-img on="tap:lightbox1" role="button" tabindex="7" src="images/portfolio/retrocam.jpg" width="215" height="215" layout="responsive"></amp-img></div>
When you save and refresh you should see a gallery on your page that looks like this:

And when you click any of the images you should see it open in a lightbox like this:

To read more about amp-image-lightbox
visit:
Wrapping Up
You now have a basic AMP page with some of the most common types of content in place, and a few little tricks to help you work efficiently and in keeping with AMP’s rules for validation.
Let’s recap the key points of what we covered:
- Always start with boilerplate code
- Use a localhost preview; MAMP recommended
- Turn on debug by adding
#development=1
to your preview URL and watching the Chrome Developer Tools console - Include Schema.org JSON metadata if you choose
- Handle inline CSS more easily via this Quick Tip
- Use AMP custom elements instead of default HTML elements where available
- Where required, load extra AMP JavaScript to drive custom elements
- Load custom fonts from Google Fonts, Fonts.com or via
@font-face
, using theamp-font
element to create fallbacks - Use in built AMP custom elements instead of custom JavaScript, e.g.
amp-image-lightbox
instead of a lightbox script
I hope this all helped to clarify the process of creating AMP pages so that you feel confident to build live sites from scratch. Now get out there and have fun making AMP accelerated websites!
HTML Templates to Practice on
Take a look at some of these landing page templates if you’d like to practice on something other than the one in this tutorial: