This article represents the first in a new group effort by the Nettuts+ staff, which covers the process of designing and building a web app from scratch - in multiple languages! We'll use a fictional Twitter-clone, called Ribbit, as the basis for this series.
In this tutorial, we need to focus on the UI. We'll leverage the popular LESS Preprocessor to make our CSS as manageable as possible.
Introduction
Be sure to download the assets for this tutorial, if working along.
This tutorial is divided into five major parts, which explain how to style various pages of Ribbit's layout. I will reference HTML elements using CSS selectors to make it easier to understand. But before diving into the layout, let's briefly discuss nesting.
Nesting
In CSS, referencing a nested element can result in lengthy selectors. For example:
someId { /* ... */ } someId div.someClass { /* ... */ } someId div.someClass p.someOtherClass { /* ... */ } someId div.someClass p.someOtherClass target { /* ... */ }
And it can grow even bigger! With LESS, you can nest one element in another, making it easier to read:
someId { /* ... */ div.someClass { /* ... */ p.someOtherClass { /* ... */ target { /* ... */ } } } }
Variables and Mixins
Create a new file and name it, style.less
. When using any style preprocessor, it's a good idea to store important colors and sizes within variables; you can easily adjust their values without searching the file, looking for property values that you need to change. We will use a handful of variables for the text color, border color, and content width:
@text-color: #3F3E3D; @border-color: #D2D2D2; @content-width: 860px;
Now, let's create two mixins. The first will create the illusion of anti-aliased text, and the second will allow for cross-browser gradients. The former is rather simple:
.antialiased (@color) { color: @color; text-shadow: @color 0 0 1px; }
The trick is to create a shadow underneath the text with the same color and a one-pixel spread, making the browser display a nice shade around the text.
Now for the gradient; this is more complicated than the anti-aliased text because every browser implements gradients differently. Once we've compensated for the various vendor prefixes, here is the code:
.gradient4f (@p1, @c1, @p2, @c2, @p3, @c3, @p4, @c4) { background: @c1; background: -moz-linear-gradient(top, @c1 @p1, @c2 @p2, @c3 @p3, @c4 @p4); background: -webkit-gradient(linear, left top, left bottom, color-stop(@p1, @c1), color-stop(@p2, @c2), color-stop(@p3, @c3), color-stop(@p4, @c4)); background: -webkit-linear-gradient(top, @c1 @p1, @c2 @p2, @c3 @p3, @c4 @p4); background: -o-linear-gradient(top, @c1 @p1, @c2 @p2, @c3 @p3, @c4 @p4); background: -ms-linear-gradient(top, @c1 @p1, @c2 @p2, @c3 @p3, @c4 @p4); background: linear-gradient(to bottom, @c1 @p1, @c2 @p2, @c3 @p3, @c4 @p4); }
Every browser has a prefix: -moz-
for Firefox, -webkit-
for Chrome, etc. The last line uses the W3C recommended version for gradients. If a browser supports it, it will override the previous properties because it's the last background
property declaration in the rule. The linear-gradient
function accepts eight parameters: four pairs of percent-color values. It creates the gradient with four color steps.
Global Styles
Let's next style some global elements, such as for buttons and links. We want all elements to use the Helvetica
or Arial
fonts with the text color defined earlier:
* { font-family: sans-serif; color: @text-color; }
Body
The body is pretty easy; we need a white background with an image-based pattern. There are no margins and padding:
body { background: white url(gfx/bg.png); margin: 0; padding: 0; }
Inputs
We'll also provide a default style for all <input/>
elements in the page:
input { width: 236px; height: 38px; border: 1px solid @border-color; padding: 0 10px; outline: none; font-size: 17px; &:focus { background: #FFFDF2; } }
We set the default size and padding, and we use the @border-color
variable to remove the annoying blue outline when the element is focused. You should notice another bit of LESS sugar: we can add CSS pseudo-classes (and normal classes too) using the &
character (parent reference), as shown here:
&:focus { background: #FFFDF2; }
This causes the input to have a light yellow background, when focused.
Submits
Submit buttons will use both the previously defined mixin and the border-radius
to create nice effect:
input[type="submit"] { height: 36px; border: 1px solid #7BC574; border-radius: 2px; color: white; font-size: 12px; font-weight: bold; padding: 0 20px; cursor: pointer; .gradient4f(0%, #8CD585, 23%, #82CD7A, 86%, #55AD4C, 100%, #4FA945); }
Links
The links should have a different color than normal text. We'll also underline them on hover:
a { text-decoration: none; .antialiased(#58B84E); &:hover { text-decoration: underline; } }
Basic Template
We will begin with the portion of the layout that remains the same in every page. Here is the HTML code, which I will explain below:
<!DOCTYPE HTML> <html> <head> <link rel="stylesheet/less" href="style.less"> <script src="less.js"></script> </head> <body> <header> <div class="wrapper"> <img src="gfx/logo.png"> <span>Twitter Clone</span></p> </div> </header> <div id="content"> <div class="wrapper"> </div> </div> <footer> <div class="wrapper"> Ribbit - A Twitter Clone Tutorial<img src="gfx/logo-nettuts.png"> </div> </footer> </body> </html>
We start with a normal doctype
definition and document head
. You can use the less.js
library and include the style.less
in the development stage (as I did in this code). Later, you can compile the LESS file into CSS, if you don't wish to use less.js
. As you've probably noticed by now, the layout is divided into three parts: header
, #content
, and footer
. You should save this HTML to see if you are styling everything correctly.
Header
Let's tackle the header
. It contains Ribbit's logo and the two words: 'Twitter Clone'. It's wrapped in a wrapper, the width of which is controlled by the @content-width
variable. There are several wrappers in the layout, and all are @content-width
wide with auto
margin:
.wrapper { width: @content-width; margin: auto; }
The header itself is 85px
tall and page wide:
header { background: url(gfx/bg-header.png); height: 85px; width: 100%; }
After the width, add div.wrapper
's style with vertical padding:
div.wrapper { padding: 11px 0; }
So the header should look like:
header { background: url(gfx/bg-header.png); height: 85px; width: 100%; div.wrapper { padding: 11px 0; }
Images in the wrapper need to be 10px
lower, in order to be nicely centered:
img { position: relative; top: 10px; margin: 0 15px 0 0; }
Also, the font in <span/>
elements must be larger than the default size:
span { font-size: 18px; margin: 0 42px 0 0; }
Here's how our design should look at this point.
Content
There's not much we can do with #content
at this time. We'll add some margin to the bottom and a minimum height; the layout will look funky if it's not tall enough:
#content { margin-bottom: 15px; min-height: 560px; }
Inside, the wrapper needs to have some vertical margin with an automatic horizontal margin:
div.wrapper { margin: 38px auto; }
Footer
Like the header, the footer is the same for all pages. We'll use a background image and a smaller font size. We'll also need to clear: both
, because we'll use floats in the content. Without clear
ing, the footer will not adjust in accordance with the content:
footer { background: url(gfx/bg-footer.png); height: 251px; font-size: 14px; clear: both; }
Let's now add some padding to the wrapper, and images within it should float to the right:
div.wrapper { padding: 15px; img { float: right; } }
Here's our footer:
The Home Page
This page displays for users not logged in to Ribbit. Therefore, it will need to present the login form in the header and a register form, with a big frog image in the content. Let's start with a basic template.
Login Boxes
Add this login form to the div.wrapper
of the header
, after the <span/>
element:
<form> <input type="text"> <input type="password"> </form>
These inputs are already styled, but we do need to add the margins and make the form display
as inline
. Append this after span
in div.wrapper
of header
:
form { display: inline; input { margin: 0 0 0 14px; } }
Register Form
Here is the HTML for the registration form:
<img src="gfx/frog.jpg"> <div class="panel right"> <h1>New to Ribbit?</h1> <form> <input name="email" type="text"> <input name="password" type="text"> <input name="password2" type="password"> <input type="submit" value="Create Account"> </form> </div>
Add this HTML within div.wrapper
of #content
. We want the image to have rounded corners and to be floated to the left (add this after margin in div.wrapper
of #content
):
img { border-radius: 6px; float: left; }
Now, we can style the registration form. It will also be a panel that we'll use later; that's why we will style the .panel
:
div.panel { border: 1px solid @border-color; background: white; margin: 0; margin-bottom: 29px; border-radius: 6px; font-size: 14px; }
For now, though, we will only style the right
panel. It's narrower and sticks to the right side of the panel. Naturally, insert the following into div.panel
:
&.right { width: 303px; height: 313px; float: right; }
Also, we need to take care of the header and content of the panel. We use <h1/>
elements for the header and <p/>
elements for content. Notice that you can use the *
wildcard inside of another element:
* { margin: 6px 0; } form { padding: 0 23px; } h1 { border-bottom: 1px solid @border-color; margin: 5px 0; font-weight: normal; font-size: 18px; padding: 13px 23px; height: 23px; } p { padding: 0 24px; margin: 18px 0; }
Here is how div.panel
's style should look:
div.panel { border: 1px solid @border-color; background: white; margin: 0; margin-bottom: 29px; border-radius: 6px; font-size: 14px; &.right { width: 303px; height: 313px; float: right; } * { margin: 6px 0; } h1 { border-bottom: 1px solid @border-color; margin: 5px 0; font-weight: normal; font-size: 18px; padding: 13px 23px; height: 23px; } p { padding: 0 24px; margin: 18px 0; } }
And here is a screenshot of how this page should look, thus far (click to see full size):
Buddies Page
The Buddies page should be displayed when a user logs in. It will display a list of the last "Ribbits," along with some statistics of your account. Once again, start with the basic template. This page, along with other pages, will display a logout button in place of the login form in the header
:
<form> <input type="submit" id="btnLogOut" value="Log Out"> </form>
The buttons have already been styled, so we only need to pin it to the right side of container and add some margins:
#btnLogOut { float: right; margin: 14px 0 0 0; }
Because this rule's selector is an element's ID, you can place it either outside of any element or within the header's div.wrapper
. It's your choice, but remember that, if you choose to place it inside of another element, the compiled CSS will have a longer selector (header div.wrapper #btnLogOut
).
"Create a Ribbit" Box
First, add this panel's code to div.wrapper
of #content
:
<div id="createRibbit" class="panel right"> <h1>Create a Ribbit</h1> <p> <form> <textarea name="text" class="ribbitText"></textarea> <input type="submit" value="Ribbit!"> </form> </p> </div>
The .right
class was styled earlier, but we need to add some styling for the <textarea/>
element. We'll give it a proper size and border:
textarea.ribbitText { width: 249px; height: 160px; border: 1px solid @border-color; }
Add this in the style definition of the right panel.
User Information
Now, let's focus on the panel, which contains the user's account information. We'll temporarily populate it with some random content to see the styling:
<div id="ribbits" class="panel left"> <h1>Your Ribbit Profile</h1> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user1.png"> <span class="name">Frogger</span> @username <p> 567 Ribbits<span class="spacing">45 Followers</span><span class="spacing">32 Following</span><br> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> </div>
It may look complex, but the structure is fairly simple, when you strip out the content:
<div id="ribbits" class="panel left"> <h1>Your Ribbit Profile</h1> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user1.png"> <span class="name"> </span> <p> <span class="spacing"> </span><span class="spacing"> </span><br> </p> </div> </div>
Regardless, we have another panel; so we need to style it first:
&.left { width: @content-width - 327; float: left; }
You probably know where to place this code (notice how easily you can perform arithmetical operations in LESS). This panel contains div.ribbitWrapper
. So, add the following code:
div.ribbitWrapper { padding: 15px 0; }
There are two <span/>
elements inside this element, each with a different color and font size. They have classes of .name
and .time
:
span { &.name { font-size: 18px; color: #58B84E; } &.time { font-size: 12px; color: #CCC; } }
We should also position the avatar image near the left border. Add the following code:
img.avatar { margin: 0 19px 0 20px; float: left; }
Also, Ribbit's text needs to be anti-aliased, justified and moved to the right. This code will place the text next to the avatar, as opposed to beneath it:
p { margin: 5px 50px 0 90px; padding: 0; text-align: justify; line-height: 1.5; .antialiased(@text-color); }
In this paragraph, there are <span/>
elements with vertical lines, visually separating them. This effect is achieved by using border, padding, and margin:
span.spacing { padding-left: 9px; margin-left: 9px; height: 10px; border-left: 1px solid @border-color; }
Buddies' Ribbits
This panel lists the latest ribbits from the people to whom the user follows. Insert the following after the user's information panel:
<div class="panel left"> <h1>Your Ribbit Buddies</h1> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user2.png"> <span class="name">Kermit</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user1.png"> <span class="name">Frogger</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user2.png"> <span class="name">Kermit</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user3.png"> <span class="name">Hypnotoad</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user2.png"> <span class="name">Kermit</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user3.png"> <span class="name">Hypnotoad</span> @username <span class="time">15m</span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> </div>
There are few example ribbits to see how it looks. We'll add some borders to visually separate them. Add this code in div.ribbitWrapper
:
border-bottom: 1px solid @border-color; &:last-child { border: none; }
This styling adds the bottom border, while removing the border on the last div.ribbitWrapper
; a border already exists on the panel.
Here is how this page should look now:
Public Ribbits Page
The "Public Ribbits" page will list the latest ribbits of profiles not marked as private, so that users can view the ribbits of those who they don't have in their buddy list. Surprisingly, there is nothing else to style, but we do need to add a touch of HTML. The only difference between this and the previous page is that this one doesn't have the user's information panel, but it will have other content in the final site. So feel free to copy the code of the buddies page, but remove this panel:
<div id="ribbits" class="panel left"> <h1>Your Ribbit Profile</h1> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user1.png"> <span class="name">Frogger</span> @username <p> 567 Ribbits<span class="spacing">45 Followers</span><span class="spacing">32 Following</span><br> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> </div>
Also change the header of the panel to "Public Ribbits." Here is the preview of this page:
Public Profiles Page
On this page, users can see a list of profiles that are not marked as private. There is also a search box to find other profiles. We'll start with the basic template.
Profile Search
The search box will use the .right
panel with an <input/>
element inside:
<div class="panel right"> <h1>Search for profiles</h1> <p> <form> <input name="query" type="text"> <input type="submit" value="Ribbit!"> </form> </p> </div>
Profiles List
Here are a handful of example profiles for the profile list, so that you can see how it looks in the browser. In a future lesson, we'll of course replace this, accordingly.
<div id="ribbits" class="panel left"> <h1>Public Profiles</h1> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user2.png"> <span class="name">Kermit</span> @username <span class="time">625 followers <a href="#">follow</a></span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user1.png"> <span class="name">Frogger</span> @username <span class="time">329 followers <a href="#">follow</a></span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> <div class="ribbitWrapper"> <img class="avatar" src="gfx/user3.png"> <span class="name">Hypnotoad</span> @username <span class="time">129 followers <a href="#">follow</a></span> <p> Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. <a href="#">http://net.tutsplus.com/tutorials/php/ ...</a> </p> </div> </div>
This page should look like:
Compiling The CSS
Referencing a nested element can result in lengthy selectors.
As I noted earlier, for production, you can compile your LESS to CSS (and I recommend you do so for performance reasons). There are a few available online compilers:
Along with some stand-alone compilers:
- Crunch! (which is a full-blown LESS editor)
- WinLess (features auto compilation when the less file changes)
- SimpLESS (includes CSS minification)
If any of these compilers do not work for you, Google for more; there are plenty! You can also use lessc
from LESS's site, but it's much easier to compile your LESS files with other compilers. Of course, there is nothing wrong with using the less.js
library to dynamically compile the layout; new browsers cache the resulting CSS.
Conclusion
As you can see, LESS is a powerful tool that makes it much easier and faster than plain CSS to style beautiful layouts.
But this is just the beginning. Be prepared to implement Ribbit's back-end in a plethora of languages and platforms in the next set of tutorials!
Comments