A Responsive Drupal theme in 50 lines of code or less
Right now everyone wants a good mobile experience for their site and that is sparking discussions about what theme you can and should use to make your site accessible to the mobile world. At Zivtech, we feel that we have outgrown starter themes because we spend more time overriding than using them (especially in D7 since a lot of the great Zen stuff is baked right in). The other day I got to thinking, just what would it take to build a responsive theme for Drupal from scratch. Is it so complicated that we need systems upon systems to handle it?
You probably guessed, my answer is no. It turns out it takes less than fifty lines of code to build a basic working responsive theme complete with media query powered break points. If a completely custom and highly flexible layout can be made in less code than zen’s opening page.tpl.php comment, why use a base theme for responsiveness? Of course, I cheated a little by using an amazing framework that a coworker and zealot turned me onto awhile back called Susy.
Grid systems have been around for quite a few years now but the advent of Sass makes them a whole new ball game. Until CSS preprocessors, working with a grid system (like blueprint or 960.gs) meant you included some boilerplate CSS files that gave you a bunch of nice default grid behaviors. Then you just had to take their selectors and add them to your markup. This works well and it is very cool, the one problem is it is not semantic. You are changing your doc to change your presentation, yeah it’s necessary but really not cool and what that ol’ zen garden was all about avoiding. Enter Sass.
Sass is all about making CSS as terse as humanly possible, so a major feature is having simple functions that you can pass variables to (called mixins) for generating all of your CSS quickly and easily. This means you can leave your markup alone (especially in Drupal where everything and its mother is a div with 10 classes) and you can generate tailored grid CSS to match. No presentation leaking into our DOM (at least until we start hitting edge cases).
Following the Drupal 7 core’s own Stark theme for guidance, I created nothing more than an .info file and a layout.css, giving me an installable theme called Tony (named for my favorite Stark (sorry, that one was for the nerds)). My goal with Tony was to create a bare bones Drupal 7 theme with a fully functional responsive layout built with media query based break points to keep the layout friendly to displays of all sizes (including mobile). Experimenting with Tony requires that you have Compass and Susy installed on your machine.
You can always find Tony in my sandbox if you want to toy around with it, though I really don’t intend to turn it into a real base theme. Remember, we have a lot of choices there and it’s what I’m trying to avoid.
Step by step
Now lets take a tour of the code:
This line just imports compass and Susy.
$total-cols: 8 $col-width: 50px $gutter: 8px $gutter-width: $gutter $side-gutter-width: $gutter-width
Here’s where the magic begins. Here we set the stage by establishing the number of columns in our grid, the width of each one, the space between each element (the gutter) and how much room we should have on the far left and right sides. These global variables will be used by Susy everywhere else to determine what CSS it should generate. Note that we are free to enter our dimensions in pixels but Susy will do all the hard math to convert these into
% ratios so that our layout can respond proportionally to the size of the screen.
@mixin full-width +columns($total-cols) +alpha
If you’re not familiar with Sass’s syntax you’re in for a treat and I highly recommend you read up. You’ll thank me. What this does is define a mixin that can be used throughout my css to reapply a few lines of code. This one sets the width of an element by proscribing it the appropriate number of columns (in this case all of them) and then applies the alpha styles (as opposed to omega) telling Susy we want this to go to the left. The first element in a row should always be an alpha and the last one an omega.
#page-wrapper +container +susy-grid-background() width: auto margin: 6em #header, #navigation @include full-width #sidebar-second +columns(2) +omega #sidebar-first +columns(2) +alpha #content +columns(6)
Here we set the stage. Again, borrowing from Stark, this theme uses Drupal 7’s stock markup without any tweaks. We apply one of Susy’s mixins with
+container to turn the
#page-wrapper div into a container, applying the global Sass variables we created before by calling Susy’s
+container mixin. We then turn on a visible grid to show our grid as a reference (obviously just for the design/development phase). Next, we set the width to auto so that the page will grow to fill any screen size and set all margins to 6em to give it a bit of breathing room and space for a background. We also set widths for the sidebars by calling the Susy mixin (again, like a function for generating CSS) and passing in an argument of the number of columns this div should take up. Note we set whether the sidebars should appear first and last by calling
+omega Susy mixins as well. You can read more about how this works here. Finally we set the main content region’s width at 6 columns (the default appropriate to both left sidebar only and right sidebar only).
body.one-sidebar.sidebar-first #content +omega body.one-sidebar.sidebar-second #content +alpha
These four lines set
#content to float right or left based on which side bar is currently visible.
body.two-sidebars #content +columns(4) #sidebar-first +alpha #sidebar-second +omega
Here we set the content to float in between the sidebars and adjust it’s width to accommodate having an extra sidebar. The grid system makes the math easy, 8 columns - (2x 2 sidebars) = 4 columns. That’s it, now we have a fluid grid based layout that can accommodate all of our side bar variations. A great start, but I did promise to make this responsive, so now the real fun begins:
Media queries allow you style sheets to apply specific styles depending on the current size of the browser. Normally these need to go a the root of your CSS document but thankfully Sass has a media bubbling behavior that will ensure they find their way to where they belong and you are free to define them in a place that makes sense to you, even nesting them inside other selectors.
@media screen and (max-width: 1000px) #page-wrapper margin: 1em top: 0
Here we specify that if the browser width ever dips below 1,000 pixels, we should remove the top margin, drop the rest of the margins from 5em to 1 and stop using up so much real estate for borders. This helps on smaller screens and netbooks, but to be able to work well all the way down to mobile, we probably want to kill our margins completely and pop the sidebars above or below our main content.
@media screen and (max-width: 500px) #page-wrapper margin: 0 body.one-sidebar.sidebar-first, body.one-sidebar.sidebar-second, body.two-sidebars #content, #sidebar-second, #sidebar-first @include full-width
And that’s it! We covered all the big ticket items in less than fifty lines of code (at least, before compiling). So if the responsive grid system and media query break points can be achieved reliably and easily without tedious calculations, tweaking or templating… Why use a base theme at all? A good mobile experience needs to be hand tweaked, that’s easier to do if you have a full control and understanding of what’s going on. We all need to change how we think about presentation and I don't think any theme out of the box will be a good mobile experience for your content.
Now all you have to do is make it look good, but that’s really a whole other post.