When first learning how to use Grid Layout, you might begin by addressing positions on the grid by their line number. This requires that you keep track of where various lines are on the grid, and also be aware of the fact the line numbers reverse if your site is displayed for a right-to-left language.
Built on top of this system of lines, however, are methods that enable the naming of lines and even grid areas. Using these methods enables easier placement of items by name rather than number, but also brings additional possibilities when creating systems for layout. In this article, I’ll take an in-depth look at the various ways to name lines and areas in CSS Grid Layout, and some of the interesting possibilities this creates.
In March this year, CSS Grid shipped into production versions of Chrome, Firefox and Safari within weeks of each other. It has been great to see how excited people are about finally being able to use it to solve real problems.
CSS Grid is such a different way of approaching layout that there are a number of common questions I am asked as people start to use the specification. This article aims to answer some of those, and will be one in a series of articles on Smashing Magazine about layouts.
The web is moving toward using HTTPS encryption by default. This move has been encouraged by Google, which announced that HTTPS would be a ranking signal. However, moving your website to HTTPS is good for other reasons, too.
Rather than debate those reasons, this article assumes you have already decided to move to HTTPS. We’ll walk through how to move your website to HTTPS, taking advantage of Varnish Cache.
In this article I’ll be taking a look at how to build a simple yet robust workflow for developing sites that require PHP and MySQL. I’ll show you how to use Vagrant51 to create and run a web server on your own computer, with the version of PHP your live site runs. I also demonstrate a process for using a hosted service to deploy files in a robust way to your live server.
This article is for you if you currently have no way to test your PHP and MySQL sites locally, or use something like MAMP2 or XAMPP3. The second half of the article will help you move away from uploading files using FTP to a process that is far less likely to cause you problems.
The Aim Of A Local Development Environment
When designing and developing your website, you should try to match the live web server as much as possible. This should include ensuring that paths from root don’t change between local and live versions, and that PHP modules and permissions are the same in both places. This approach will reduce the possibility of something going wrong as you push the site live. It should also enable you to come back to a site to make changes and updates and know that you can then deploy those changes without breaking the running site.
A good local development environment saves you time and stress. It gives you a place to test things out. It means that you can pick up a project, make some changes, deploy them and bill your client for another job well done.
If you keep a list of changes made to your site and then transfer the files one by one, you leave yourself open to difficulties caused by human error and connectivity problems. Many issues we see supporting our products are down to failed FTP transfers. A key file has failed to upload, and it is deep in the core product. It’s easy to forget to transfer a file, and it’s also easy to leave old files lying around. If the software you use has removed some files to resolve a security issue, leaving them on the server could leave you at risk even if you have upgraded.
A good deployment method ensures that the files on your live server exactly match those locally. If anything fails to deploy, you should be notified so you can fix the issue before your client or their customers see it first!
Step 1: Grab Some Tools
We’re going to be using some free tools to create our development environment. First, download VirtualBox4, a free application which allows you to run a virtual machine on your computer. You may have already come across virtual machines if you work on a Mac and use a Windows virtual machine for testing. A virtual machine is exactly as the name suggests, a complete virtual OS running on your computer.
Install the version of VirtualBox for your operating system.
Now download and install Vagrant51. Vagrant is an application that helps you manage virtual machines.
It’s possible to work with virtual machines without using Vagrant. However, each time you want to set up a new VM you have to go through the process of installing web server software and configuring the server. Vagrant helps you automate that process so that within a few minutes you can have a local web server running your site.
If you are on Mac OS X or Linux, at the command line run the following command:
sudo vagrant plugin install vagrant-bindfs
For all operating systems, run the next command to install Vagrant Host Manager8 to save you editing your hosts file by hand.
sudo vagrant plugin install vagrant-hostmanager
Vagrant requires a project folder with a text file saved with the name Vagrantfile in the root. In the Vagrantfile you specify how the VM should be set up. You can write your own configuration scripts for Vagrant, but for most cases you don’t need to as someone else has already done the hard work for you. Here we’re going to use a tool called PuPHPet9.
PuPHPet is an online configuration tool that helps you configure a Vagrant project. You work through a form on the website, selecting options for your site, and then download a package containing a Vagrantfile and other scripts to set up a virtual machine.
Step 2: Discover What Is On Your Live Server
To use PuPHPet to set up a development server that is as close as possible to the hosting you will use for the site, first find out what is on the live server. You want to know:
Type of Linux
Web server: Apache or Nginx (probably Apache if shared hosting)
PHP version: this will be something like PHP 5.4 or 5.5, etc.
The configured resource limits for file upload, memory and so on
Installed PHP modules; for example: gd, curl
If you don’t yet have access to the hosting then you will need to ask the host these questions. If you do have access then you can find out for yourself.
Upload a file to the server named info.php that contains the following PHP function:
You should see an indication of the base operating system in the first line of the report “System”. Knowing that you have a Debian, Ubuntu or CentOS system might be helpful for more advanced configurations.
2. Web Server
This is probably Apache. If you see any mention of Apache in the initial section or in the headings below, it’s Apache. The most likely alternative is Nginx. For simple sites the biggest difference between web servers is the fact that rewrite rules are different, so if you are creating friendly URLs you need to know the correct syntax to use.
3. PHP Version
The version of PHP will be right at the top of the document next to the PHP logo. It might be a long string — you are mostly interested in one number after the dot. So if you see “PHP Version 5.4.4-14+deb7u14,” all you need to note down is PHP 5.4.
4. PHP Modules
PuPHPet will install some default modules for you. If you want to be sure certain things are present, however, you can specify them. The PHP modules are listed, with details about them, after the “Core” section of the report. Common modules to look out for are:
curl: for making requests to other servers
gd and/or imagemagick: used for image manipulation
mysql, mysqli and pdo: methods of connecting to the database. You should probably be using mysqli or pdo at this point
5. Resource Limits And Configuration Options
Under the section “Core” you will find all kinds of information about PHP. Useful settings to note down are:
max_execution_time: how long a script may run for
max_file_uploads: how many files may be uploaded at once
max_input_vars: how many fields a form is limited to
post_max_size: the maximum size of a form post
upload_max_filesize: file upload limit
Depending on your hosting, some of these might be able to be changed. For example, you can usually increase the size of files that can be uploaded.
6. MySQL Version
Under the PHP module information for mysql, mysqli and pdo_mysql you should see a value for “Client Library Version”: this is your MySQL version. Again, knowing just one value after the dot is fine.
Beware Of Old PHP!
On doing this test, if you discover that the server is running anything older than PHP 5.4 — stop now and find out how to upgrade the hosting to a more recent PHP version. For a new site I’d suggest ensuring you are on at least PHP 5.5. Version 5.6 is even better.
PHP5.3 is not only end-of-life, it’s also really slow in comparison to newer PHP versions. It’s a good plan to make sure you are using a supported version of a core technology on your site. Through helping customers at Perch we’ve found that, in general, hosts are happy to upgrade you to a newer server if you put in a request. If they are not, I’d seriously consider moving hosts13.
Step 3: Build A Project With PuPHPet
Now that you have your information to hand, you can use it to build a project with PuPHPet that reasonably closely mirrors your environment. I’ll walk you through the interface. If I don’t mention a setting and you don’t have an opinion about it, then leave the default value.
On the PuPHPet website14, choose Deploy Target → Locally in the sidebar. In the main screen select VirtualBox as the provider.
Under Distro you can select the type of Linux you are using, if it is listed. If it isn’t listed I would suggest using the default Ubuntu.
The IP address needs to be something unique on your network, not a real external IP. I tend to use IP addresses with the format 10.1.0.130 for VMs.
The hostname identifies your server. Again this can be something made up.
Shared Folders is an important setting. When you use a virtual machine you are running an entirely new computer with its own file system on your computer. If you want to continue editing files in the usual place on your computer — and not have to transfer them into the VM to view them — you need to map the drive on your own machine to one on the VM. That’s what we are doing when we create a shared folder.
On my Mac, inside /Users/rachel/Sites I have a folder called vm. This is where I place a folder for each of my projects. When I set up a VM I use the path /Users/rachel/Sites/vm as the folder source, mapped to /var/www as the folder target.
If this is a new site and you don’t already have files created, at this point I’d suggest creating a folder for the project you are setting up the virtual machine for, and pop an index.html into that folder with <h1>It works!</h1> in it. Just so you can see that things are working after running setup.
Finally, if you are on Mac OS X or Linux, select NFS as the shared folder type.
You can probably leave everything here as the default. It’s worth knowing that under this option you can configure cron jobs for scheduled tasks and add system packages if you have certain things you want to install.
Unless you have identified that you have Nginx, select Apache and check Install Apache. This will open up a further set of options. Here is where you configure your virtual hosts.
A virtual host means that instead of having one website per server you can have multiple websites on a server. With virtual machines you can create as many as you like, so it’s up to you whether you configure a single virtual host or more. What you should not do is configure one virtual host and then stick multiple websites into subfolders of that host. Each site needs either its own VM or a virtual host on a VM so that the path of your files from root does not change when you go live.
The basic settings for a virtual host are as follows:
Server name: clientname.dev or any made up domain you like.
Document root: from /var/www. If you have shared a folder in the way I suggested, /var/www is that directory on your computer — the directory with all your project folders in it — so you can specify /var/www/clientname here.
If you want to add another host, scroll down to Add an Apache vhost and create your next one.
Select PHP and check Install PHP.
Under PHP Version select the version you identified as being on your host.
Under PHP Modules add any specific modules (for example, “gd” and “curl”) that you identified as present on your hosting.
Select MySQL and if you know the version of MySQL select it here.
You can now create a database user with a password. I tend to just use the name “vagrant” for both on local development machines.
You can also create a database ready to use for your site. Remember these details as you’ll need them to install your CMS or use in your own custom code that connects to MySQL.
If you are using a CMS then it’s a good idea to have some way of looking at the emails it sends. PuPHPet suggests you install MailCatcher21 locally for this task as it saves configuring a mail server.
That should be it for setup. Select Create Archive from the sidebar and download your file. Unzip the file and put it somewhere on your system — mine all live in my home directory in a subdirectory called vagrant.
Your First Virtual Machine
You are almost ready to go. Open up a terminal window and change into the folder where you unzipped your project.
Now run the command:
The first time you do this it will take a while. Vagrant will see that you don’t already have the base operating system downloaded so it will download it. When you create a new project in the future and use the same version of Linux, Vagrant will copy the box so this will be quicker.
You will see a lot of stuff scrolling by — don’t worry about it; it will take a few minutes to get everything set up for you. If you are using NFS you will be prompted for your password during the process to allow Vagrant to edit your exports file.
Once Vagrant has finished you should be able to go to the domain you set up for your virtual host using your web browser and see your site! If you make changes to your files and reload the browser, you will see your changes.
Basic Vagrant Commands
Vagrant is controlled with a few simple commands from the command line. We’ve already used vagrant up which will start up a VM. If the VM is brand-new it will also provision it — setting up the packages you configured to be installed, creating your virtual hosts, and so on. If you run vagrant up on a VM that has already been provisioned, Vagrant will not reprovision it.
Understanding the commands and what they will do is important, but if you prefer to stay out of the command line, take a look at Vagrant Manager24. Vagrant Manager is an application for Mac OS X and Windows that gives you a nice way to manage your VMs and also see which are running at any one time.
If you want to reprovision a VM, first make sure it is running with vagrant up, then type:
To stop a VM from running you can use:
This will pause the box and give back your host machine the memory it uses, but it won’t delete anything on the VM or shut down the operating system. If you run vagrant up again, it will come back just as it was before you paused it.
To shut down the operating system on a VM use:
Running vagrant up on a halted box boots up the system again.
If you want to set your virtual machine right back to its initial state, run:
This will delete anything you installed on the server. It won’t touch the files in your mapped drive as those are hosted on the host computer, but it will delete MySQL databases, for example. If you want the data from those, export it first.
To access the command line on the VM type:
You will then be on your VM and can run any commands. A common thing you might do is import or export a database file.
Importing A Database File
Our process creates an empty database. If you are installing a CMS or some other software, it is likely that it will create the tables for you. If you want to import a file exported from your live server, you can do that at the command line.
Use vagrant ssh to reach the command line of your VM. Make sure your exported database SQL script is in the root of your site, within the shared folder. Then, type the following (I’m assuming a database name of dbMySite, username and password both set to “vagrant”.
mysql -u vagrant -p dbMySite < /var/www/clientname/db.sql
You will then be prompted for the password. Type your password and the database will be imported.
After following these steps you should have a nice way to work locally on one or more projects at a time. You can set up virtual machines that are similar to live, and you are not developing in subfolders. We can continue to enhance our workflow by moving away from FTP to using a deployment service.
Get Files Into Source Control
If you are already using Git, then you are part of the way to simple deployments. If not, that is the first thing we need to do. Our process will be to commit files into Git on our own computer, then create an account on a hosted Git repository and push our files to the live server from there.
Stay on the command line and change to the directory where you keep your site files. If your files are in /Users/rachel/Sites/vm/clientname, you would type:
The next command tells Git that we want to create a new Git repository here:
We then add our files:
git add .
Then commit the files:
git commit -m "Adding initial files"
The comment in quotes after -m is a message describing the commit. Your local files are now in Git! As you work you can add and commit files.
If you would rather not work in the command line, there are plenty of applications to help you work with Git. Tower28 is a popular choice. The people who develop Tower have also produced a great book on learning version control with Git29. You can read online free or purchase an ebook version.
Create A Hosted Repository
To make deployments easy we are going to use a hosted Git repository that will securely store your files and allow you to deploy them live. The hosted service I’m suggesting here is Beanstalk32, because it does both hosted Git and deployment. There are other services that will deploy from a GitHub account or other hosted Git service; Beanstalk bundles them together which makes things straightforward.
After setting up your Beanstalk account, create a repository there. You now need to push your files to that repository. At the command line, make sure you are inside the directory that contains your files, and type:
Your files will now be transmitted to Beanstalk. You should be able to see and browse around them there.
We can now edit our files, previewing them on our own web server. We can commit them locally and push the changes to Beanstalk. Our final step is to make them live.
Deployments on Beanstalk can be manual or automatic. An automatic deployment would happen when you pushed some code into your branch on GitHub. Typically, you’d do this on a staging environment where it wouldn’t matter if the code you pushed broke things.
For a live site, especially working in our simple way, you’ll want to do a manual deployment. You will log into Beanstalk and trigger the deployment yourself.
When you deploy, Beanstalk will make sure that the files on the server are identical to the files in Git. If a new file is present, it will be added, changed files will be updated, and any files deleted from Git will be removed from the server.
To get started deploying your files, go to the repository that you created on Beanstalk and select Deployments. Here you can create a new environment and server. If you are creating a deployment to the live server, name it “Live” or “Production,” keep Deployment Mode as Manual and specify the master branch.
You can then add a server type. If you are deploying to shared hosting, that type would ideally be SFTP, but could also be FTP. On the next screen you then add your server details. These are exactly what you would use to connect with an FTP client.
Beanstalk allows you to run a test to check that your server can be connected to. Once you have your server set up, and have verified the connection, you are all set to deploy your files to your live site. It’s as simple as clicking a button and waiting. Once you deploy, Beanstalk will do the following:
Connect to your server.
Ensure that the files on the server match the files in the branch you are deploying.
On initial deploy, all existing files on the server have to be checked. Your first deploy will be slow!
Subsequent deploys only change things that have changed in Git.
Here are a few suggestions to make deploying your sites in this way easier.
Create A Multiple-Server Config File
Working across a few environments means you are going to need to manage things that are specific to each environment, such as database settings or file paths. I like to create a config file that switches on host name, so the same file can be everywhere and you can’t accidentally replace your live details with the development server ones. You can see an example for Perch38, but you could do the same for any other system that has a config file as part of the code.
Use .gitignore To Keep Things Out Of Beanstalk
There are likely to be files and assets that you don’t want to push to Beanstalk. You can use a .gitignore file to make Git ignore them. There are some good starting point files39 for various systems on GitHub.
Exclude Files And Folders On GitHub
If you want files and folders to end up on Beanstalk as part of the repository but not be deployed onto your server, you can also exclude them from the deployment. Perhaps you have some source assets you want to manage in Git along with the site, but don’t want to deploy. You can configure patterns, specific files or directories when setting up or editing your deployment.
Edit Files On Beanstalk In Emergencies
When you are away from your desk and need to fix a problem, Beanstalk can save the day. You can edit a file directly on Beanstalk using the web interface and push it live.
This is not as good as testing locally before deploying but handy in an emergency. Unlike editing directly on the server, you have the safety net of being able to roll back to a previous version if it all goes wrong. You also have the changed file in Git so you don’t overwrite the change next time you deploy.
Use Navicat To Sync Database Changes
One of the biggest problems of deploying changes can arise if you need to make changes to the live database to keep it in sync with your local one. Navicat40 can help with that job. You can select a source and target, compare differences and run queries to make changes.
Your New Workflow
If you’ve followed this article you should now be in a position to develop one or many sites locally, using a setup similar to how the site will run on the live server.
Your files are now version-controlled and are pushed to a remote Git repository.
You can deploy in the confidence that what ends up on the live server is exactly what should be on that server — no more, no less.
When you need to make changes to a project in the future, you can make sure you have the latest files from Beanstalk, make your changes, test, commit and deploy, and not worry that you might break something. The time you have spent getting your workflow straight should pay off the first time you need to make updates to a running site that you haven’t touched for a few weeks.
This isn’t the only way to achieve a solid development environment and deployment process, but it’s a reasonably straightforward one. Once you understand this type of workflow, you can explore how to streamline it further, making time to do more interesting things than fight with servers and hosting!
I’m a firm believer that the best way to optimize for fast-loading mobile sites is to optimize for everyone. We don’t know when someone is on a non-mobile device but tethered to their phone, or just on awful Wi-Fi.
In a previous article for Smashing Magazine1 I explained how you can speed up your websites by serving dynamic pages from a reverse proxy like Varnish. If you are new to Varnish then that article is the place to start as I’ll be diving straight into configuration details here.
In this article I’ll explain how you can benefit from using Varnish even when there are parts of your pages that can’t be cached for long periods, using Edge Side Includes.
The Problem With Caching
The bulk of most web pages is content that doesn’t change very often. Generally, when you publish some content it remains fairly static, and even if it is updated, taking a few minutes before the new version appears may not be a problem. If it is vital that the cache is invalidated on publish, you can use cache invalidation2 to clear that page from the cache. However, there might be some content that you do not want to cache at all, such as personalized content. There may also be content that you would like to cache but for shorter periods of time, perhaps a news widget that updates very frequently. When pages contain this type of content you might think they are uncachable. It is here, though, that Edge Side Includes (ESI) can save the day – and your site performance.
What Are Edge Side Includes?
ESI3 is a language specification for assembling fragments of web pages inside other web pages. The specification was submitted to the W3C in 2001, but remains a “W3C Note” made available for discussion and not endorsed by the W3C or updated by a Working Group.
ESI works in a similar way to other methods of including fragments in your pages, such as Server Side Includes (SSI) or PHP include statements, but it has been designed for reverse proxies like Varnish that sit in front of a web server and cache content.
How Does ESI Work With Varnish?
Varnish has implemented a subset of ESI features, three of the available seven statements. The supported statements are listed in the Varnish documentation4. This means that we can use ESI on our pages and tell Varnish to cache an include for a shorter time than the main document, or even not cache the include at all. If you are already up and running with Varnish, it’s really simple to get going with ESI and make a huge difference to your hit rate: the number of pages served from the cache.
If you want to follow along and don’t have a Varnish install to play with, you can download my Vagrant package from GitHub5 which will install a basic LAMP server and Varnish. Have a look at the README on GitHub to see how to configure that for your environment.
I have a layout using an open source Bootstrap template, which is a standard blog layout. In the sidebar I intend to place some content that will be frequently updated and I want to ensure that it is not cached for too long. The blog post and other content I want to be cached for several minutes, as it won’t change often.
Make The Sidebar An Include
The first thing to do is make the sidebar a PHP include. I save the include as inc/sidebar.php and then add a PHP include to the main page to include it. My page looks the same but the content of the sidebar is now separated into an include.
In my main page and in my include I am outputting the current date and time. This just helps me see whether my cache is working. If I was doing no caching, each time I reload the page I would expect the time to change. With Varnish in a default state both times should be the same, and they will update only when the cache clears.
Include The File Using ESI
I’m now going to use two ESI tags that are supported by Varnish:
The esi:include tag is used to include the file via ESI. So I replace my PHP include with this:
Create A Fallback When ESI Are Not Available
Straight after the esi:include tag we add esi:remove tags, and inside those tags is the original PHP include. While testing I have also added the line “Not ESI!” so that I can see if the include is being added by PHP or by ESI.
If you now load the page on a server running Varnish that does not have ESI configured you should see the “Not ESI!” text. ESI isn’t running so the original PHP include will be loaded.
The esi:remove tags enable you to create a fallback for situations where Edge Side Includes are not available. This might be because Varnish hasn’t been configured to use them for that page, or you are serving the site directly through Apache, as might be the case in development.
Where ESI are available the entire <esi:remove></esi:remove> block is removed from the page when includes are parsed. The use of the esi:remove statement doesn’t stop the PHP code from being executed. It will still be executed each time the page is loaded if ESI is not available, and according to caching policy if it is.
An alternate option to using esi:remove is to use the third statement supported by Varnish, <!--esi … -->. You would use this if you want nothing to happen when ESI is not available.
<!--esi <esi:include src="inc/sidebar.php"/> -->
In the above case, if ESI were unavailable the sidebar would not be loaded – the browser treats the statement as an HTML comment – so nothing would be visible to a site visitor. If processed by ESI, the comments would be removed and the include parsed.
Configure Varnish To Use ESI
We tell Varnish to use ESI in our VCL, within a vcl_backend_response subroutine.
set beresp.do_esi = true;
Note: in Varnish 3 you would put this inside sub vcl_fetch. See the other differences between Varnish 3 and 4 in these upgrading notes10.
If you restart Varnish and reload your page you should find that the “Not ESI!” text has gone, and if you View Source there is no trace of the includes but the sidebar is being included. Our times should remain the same, however, as we are still caching both parts of the page in the same way.
Tweak The TTL
I want my sidebar to only be cached for 30 seconds before it is refreshed, and my main content 120 seconds. Inside the vcl_backend_response subroutine add the following:
if (bereq.url ~ "sidebar.php")
set beresp.ttl = 30s; else
set beresp.ttl = 120s;
Here I am saying that if the URL matches the string sidebar.php cache only for 30 seconds, otherwise cache for 120 seconds.
Save the VCL and restart Varnish, then reload your page – both times should be the same. Wait 30 seconds and reload the page – the sidebar time should update but the main time remain the same. You are now caching these page components differently and assembling the page with ESI.
The value passed to bereq.url is a regular expression. Something you might like to do is put files that are uncachable or have a common TTL into one folder, then target that folder.
if (bereq.url ~ "^/inc")
set beresp.ttl = 30s; else
set beresp.ttl = 120s;
If there is an include that you never want to be cached (for example, if it contains some personalized content), you can selectively flag things as uncacheable and deliver them directly from the web server.
I have a folder named personalized and I want any includes inside that folder to be served from the web server directly, bypassing the cache. I can do this using a similar if statement to the one I used to set the TTL, again in vcl_backend_response.
if (bereq.url ~ "^/personalized")
set beresp.uncacheable = true;
The line return(deliver); means that the content bypasses the cache altogether and will always be served fresh, while the rest of the page gets served from the cache. You can test this by creating an include, again with a time on it, and placing it in the personalized folder.
This content should always show an updated time as it is served from the cache.
The full VCL can be found on GitHub. You can try different combinations of ESI to meet your own caching requirements.
Edge Side Includes can be a powerful way to tweak the performance of Varnish. How you use them, though, is likely to be very specific to your own site. Hopefully this article has highlighted some of the possibilities. To finish, here are some links that I’ve looked at while writing this tutorial and some which detail implementation for various CMSs. Check carefully which version of Varnish the article refers to – there is a lot of Varnish 3 or even Varnish 2 information about and the syntax has changed significantly. However, converting from one to the other is generally straightforward.
If you mention printing with CSS to many people who work on the web, print style sheets1 are the use that comes to mind. We are all well used to creating a style sheet that is called upon when a web document is printed. These style sheets ensure that the print version is legible and that we don’t cause a user to print out huge images. However, CSS is also being used to format books, catalogs and brochures — content that may never have been designed to be a web page at all.
In this article, we’ll take a look at the CSS modules that have been created not for use in web browsers, but to deal with printed and paged media. I’ll explain how the selectors, properties and values that they introduce work. I’ll finish up with a working example that you can use as a starting point for your own experiments. For that example, we’ll need a user agent that supports this specialized CSS. I’ll be using Prince, which is a commercial product. However, Prince has a free version that can be used for non-commercial use, making it a good tool to try out these examples.
Why HTML And CSS Make Sense For Print
It may seem a bit strange that content not particularly destined for the web should be maintained as HTML and formatted with CSS. It seems less strange when you realize that popular eReader formats such as EPUB and MOBI are HTML and CSS under the hood. In addition, even if the entirety of a manuscript or catalog isn’t to be published on a website, some of it likely will be. HTML becomes a handy format to standardize on, far easier to deal with than having everything in a Word document or a traditional desktop publishing package.
The Differences Between CSS For The Web And CSS For Print
The biggest difference, and conceptual shift, is that printed documents refer to a page model that is of a fixed size. Whereas on the web we are constantly reminded that we have no idea of the size of the viewport, in print the fixed size of each page has a bearing on everything that we do. Due to this fixed page size, we have to consider our document as a collection of pages, paged media, rather than the continuous media that is a web page.
Paged media introduces concepts that make no sense on the web. For example, you need to be able to generate page numbers, put chapter titles in margins, break content appropriately in order that figures don’t become disassociated from their captions. You might need to create cross-references and footnotes, indexes and tables of content from your document. You could import the document into a desktop publishing package and create all of this by hand, however, the work would then need redoing the next time you update the copy. This is where CSS comes in, whose specifications are designed for use in creating paged media.
Because the specifications are designed for paged media, we won’t be considering browser support in this article — it wouldn’t make a lot of sense. Later on, we’ll look at a user agent designed to turn your HTML and CSS into a PDF using these specifications.
The @page rule lets you specify various aspects of a page box. For example, you will want to specify the dimensions of your pages. The rule below specifies a default page size of 5.5 by 8.5 inches. If you intend to print a book, perhaps by a print-on-demand service, then finding out the sizes you can use is important.
size: 5.5in 8.5in;
In addition to specifying sizes with length values, you may also use paper size keywords, such as “A4″ or “legal.”
You may also use a keyword to specify the page’s orientation — “portrait” or “landscape.”
size: A4 landscape;
Understanding the Page Model
Before going any further, we should understand how the page model for paged media works, because it behaves somewhat differently to how things work on screen.
The page model defines a page area and then 16 surrounding margin boxes4. You can control the size of the page area and the size of the margin between the edge of the page area and the end of the page itself. The table in the specification explains very well how these boxes are sized.
The page area is the space on the page into which your page’s content will flow. When it runs out of room, another page will be created. The margin boxes are used only for CSS-generated content.
Left and Right Page Spreads
Another aspect of the page model is that it defines pseudo-class selectors for the left and right pages of your document. If you look at any printed book you have on hand, you’ll probably see that the margin’s size and the margin’s content are different on the left and right pages.
We can use these selectors to define different margin sizes for our pages.
Two other pseudo-class selectors are defined. The :first selector targets the first page of a document.
The :blank pseudo-class selector targets any page that is “intentionally left blank.” To add this text, we can use generated content that targets the top-center margin box.
@top-center content: "This page is intentionally left blank."
Generated Content and Paged Media
In the last example, we used CSS-generated content to add the text to the top-center margin box. As you will discover, generated content is vitally important to creating our book. It’s the only way things can be added to our margin boxes at all. For example, if we want to add the title of the book to the bottom-left margin box of right-hand pages, we would do this using generated content.
Also part of the “Paged Media” specification is information about how to control page breaks. As already described, once the content fills a page area, it will move onto a new page. If a heading has just been written to the page, you might end up with a page that finishes with a heading, with the related content beginning on the next page. In a printed book, you would try to avoid this situation. Other places you might want to avoid a break are in the middle of a table and between a figure and its caption.
Starting a new chapter of a book with an h1 heading is common. To force this heading to always be the beginning of a page, set page-break-before to always.
To avoid breaks directly after a heading, use page-break-after.
h1, h2, h3, h4, h5
To avoid breaking figures and tables, use the page-break-inside property.
Books are all about numbering things — pages, chapters, even figures. We can actually add these numbers via CSS, saving us from having to renumber everything because we decided to, say, add a new figure partway through a chapter. We do this using CSS counters7.
The obvious place to start is with page numbers. CSS gives us a predefined page counter; it starts at 1 and increments with every new page. In your style sheet, you would use this counter as the value of generated content, to put the page counter in one of your margin boxes. In the example below, we are adding page numbers to the bottom-right of right-hand pages and the bottom-left of left-hand pages.
You can create your own named counters and increment and reset them as you require. To create a counter, use the counter-reset property, increment it with counter-increment. The CSS rules below will create a counter for chapters named chapternum and increment it with each h1 — being the start of a chapter in this book. We then use the value of that counter in generated content to add the chapter number and a period before the chapter’s actual title.
content: counter(chapternum) ". ";
We can do the same for figures in the book. A common way to number figures is to use chapternum.figurenum. So, “Figure 3-2″ would be the second figure in chapter 3. On the h1, we could reset figurenum in order that it starts from 1 for each chapter.
Take a look at a printed book again. As you leaf through a chapter, you’ll probably see that the chapter’s title is printed on the left or right page. As strange as it may sound, the “Generated Content for Paged Media” specification lets us achieve this using CSS.
We do this using a property named string-set in the selector that we want to take the content from. For the chapter title, this would be the h1. The value of string-set is the name you would like to give this content and then content(). You can then output this as generated content using string().
When your paged media is generated, each time an h1 occurs, the content is written to doctitle and then outputted in the top-right margin box of right-hand pages, changing only when another h1 occurs.
Footnotes are a part of the “CSS Generated Content for Paged Media Module8” specification. The way footnotes work is that you would add the text of your footnote inline, wrapped in HTML tags (probably a span), with a class to identify it as a footnote. When the page is generated, the content of that “footnote element” is removed and turned into a footnote.
In your CSS, use the footnote value of the float property to create a rule for your footnote class.
In your document, use that class to wrap any footnote text.
<p>Footnotes<span class="footnotes">Footnotes and notes placed in the footer of a document to reference the text. The footnote will be removed from the flow when the page is created.</span> are useful in books and printed documents.</p>
Footnotes have a predefined counter that behaves in the same way as the page counter. Typically, you will want to increment the counter by 1 each time a fn class occurs and reset it at the beginning of each chapter.
The various parts of a footnote can be targeted with CSS pseudo-elements. The footnote-call is the numeric anchor in the text that indicates there is a footnote. This uses the value of the footnote counter as generated content.
The footnote-marker is the numeric marker placed in front of the footnote text in the footer of your document. These behave in a similar way to the numbers generated for an ordered list in CSS.
The footnotes themselves are placed in the margin, within a special area of the page named @footnotes. You would target and style that area as follows.
border-top: 1pt solid black;
Before moving on to a working example of everything we’ve learned, let’s look at cross-references. On the web, we cross-reference things by adding links. In a book or other printed document, you would normally refer to the page number where that reference is to be found. Because page numbers might change according to the format that the book is printed in — and between editions — doing this with CSS saves us from having to go through and change all of the numbers.
We use another new property, target-counter, to add these numbers. Start by creating links in your document, giving them an href, which is the ID of the element in the document that you want to target. Also, add a class to identify them as a cross-reference, rather than an external link; I’m using xref.
We’ll be looking at this technique in practice when we create a table of contents for the working example.
Putting It All Together: An Example Book
We’ve looked at a lot of different properties here in isolation. They make more sense once you put them to use by building a book.
To actually create a book using this CSS, you’ll need a user agent that supports it. Currently, very few things implement this specification well; the one that is most accessible is Prince129. A standalone commercial license for Prince is expensive, however, you may use Prince free of charge for non-commercial projects. This means that if you just want to try out these techniques, you can. Additionally, if you do have non-commercial uses for this technology, you may use Prince to format those books.
I have extracted passages from one of my favorite books on Project Gutenberg, Our Cats by Harrison Weir10. I’ve chosen this book because I like cats and because it has images and footnotes that I can use to demonstrate formatting.
You can find the files I am using, plus a generated PDF, over on GitHub11. If you want to experiment with the CSS and build the book yourself, then you will need to download and install Prince129. Prince is a command-line tool on the Mac, and although there is a Windows GUI, I’ll use the command line because it really is very simple.
Using a Terminal window, switch to your book’s directory or the location where you downloaded my files from GitHub.
Now, run Prince:
prince -s pdf-styles.css book.html builds/book.pdf
This will create a PDF in the builds folder named book.pdf. Now, if you make any changes to the CSS or HTML, you can run Prince to see what is different.
The HTML Document
My entire “book” is compiled in a single HTML document. Compiling documents in Prince is possible, but I’ve found it simpler to just deal with one large document. Before the chapters, which start with an h1, I have a div that contains the cover image, and then the table of contents for the book.
The table of contents links to the IDs of the chapters’ h1 headings.
<html dir="ltr" lang="en-US">
<meta charset="utf-8" />
<title>Our Cats and All About Them</title>
<meta name="author" content="Harrison Weir"/>
<meta name="subject" content="Cats. Their Varieties, Habits and Management; and for show, the Standard of Excellence and Beauty"/>
<meta name="keywords" content="cats,feline"/>
<meta name="date" content="2014-12-05"/>
<h1>Extracts from Our Cats and All About Them by Harrison Weir</h1>
<li><a href="#ch1">The First Cat Show</a></li>
<li><a href="#ch2">Trained Cats</a></li>
<li><a href="#ch3">General Management</a></li>
<li><a href="#ch4">Superstition and Witchcraft</a></li>
<h1 id="ch1" class="chapter">The First Cat Show</h1>
<h1 id="ch2" class="chapter">Trained Cats</h1>
<h1 id="ch3" class="chapter">General Management</h1>
<h1 id="ch4" class="chapter">Superstition and Witchcraft</h1>
The CSS then uses all of the things we have described so far. To start, we need to set up a size for the book using the @page rule. We then use the :first pseudo-class selector to remove the margin on page 1, because this page will have the cover image.
Next, we deal with the specifics of the left- and right-hand pages, using the :right and :left spread pseudo-classes.
The right-hand spread will have the title of the book in the bottom-left margin box, a page counter in the bottom-right, and the chapter’s title in the top-right. The chapter’s title is set using string-set further down in the style sheet.
The next section of the style sheet deals with counters. In addition to the preset page counter, we are defining counters for chapters and figures.
/* Reset chapter and figure counters on the body */
counter-reset: chapternum figurenum;
font-family: "Trebuchet MS", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Tahoma, sans-serif;
/* Get the title of the current chapter, which will be the content of the h1.
Reset figure counter because figures start from 1 in each chapter. */
string-set: doctitle content();
/* Increment chapter counter */
content: counter(chapternum) ". ";
/* Increment and display figure counter */
content: counter(chapternum) "-" counter(figurenum) ". ";
Chapters now have their number placed before the title. Figures also display their number.
We create footnotes as in the explanation earlier, superscripting the footnote’s call.
We then add some rules to control where pages break. You need to be fairly careful about being too heavy handed with this. If your book has a lot of tables and figures, then adding many specific rules here could cause a lot of long gaps in the book. Experimenting and testing will show how far you can take the control of breaks. I have found the rules below to be a good starting point.
Remember that this is a suggestion to the user agent. In some cases, keeping a table from breaking will be impossible if the table doesn’t fit on a page!
Finally, we style the table of contents, and we use an interesting trick here. When describing cross-references, I explained how we use target-counter to display the page number that the ID is on. This is what we’ll do for our table of contents. The rule below puts the page number after the link to each chapter in the table of contents.
Commonly in books, however, you would use leader dots to line up all of the page numbers against the right margin. Amazingly, CSS gives us a way to do this, by adding leader() before the number in the generated content.
We now have a complete style sheet with which to build our book. I’ve avoided spending a lot of time on typography here, concentrating instead on the specifics of creating a book. From this point, however, you can experiment and add your own styles to create a unique book design.
Not Just Books!
Remember that these techniques are not just for books. You could use them to generate print and PDF versions of a product catalog directly from the HTML of a website that you have developed for a client. Or you could create flyers and brochures from web content.
If you want to create PDF documents from a website using Prince, then DocRaptor27 is a great option. This service uses Prince via an API. You can send documents via the API and receive a PDF — perfect for allowing users to download content as a PDF on the fly. Everything we have looked at in this article is possible via an API integration with DocRaptor.
Even if you don’t have an immediate need for PDF generation, it’s a fascinating aspect of CSS — and it’s a useful skill to have tucked away, so that you know what is possible when a use case presents itself.