How to Build a Lightsaber (with CSS)
Lately there’s been a huge number of CSS3 experiments on the web. And while they’re all doubtlessly cool, very few go through and explain how it was done in tutorial form. Most simply give a list of properties used or just show their code, without explaining the why of their source. In this tutorial, I’m going to walk you step by step in the process of building a detailed lightsaber using html, CSS, and even a little jQuery for interactivity. Let’s get started!
View Demo
HTML
<!DOCTYPE html> <html> <head> <title>Feel the power of a CSS3 Lightsaber!</title> <link href="style.css" rel="stylesheet" type="text/css" media="screen" /> </head>
The first step is just to place the doctype, opening html tag, and the head section containing the title and a link to the stylesheet. Nothing to fancy going on here, just basic html tags.
<body>
<h1>FEEL THE POWER OF A CSS3 <span>LIGHTSABER!</span></h1>
Here I opened the body tag, and placed some text in an h1 tag. Pay attention to the nesting of the tags around “lightsaber,” as this will become important once we code the jQuery.
<div id="container"> <div id="blade"></div> <div id="hilt"> <div id="opening"></div> <div id="circles"> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle></div> </div><!--end circles--> <div id="switch"></div> <div id="line"></div> <div id="bars"> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> <div class="bar"></div> </div><!--end bars--> <div id="end"></div> <div id="hiltMain"</div> </div><!--end hilt--> </div><!--end container-->
This is where the bulk of the markup comes in, using many empty div’s and a few nested to form the structure of the lightsaber. Unfortunately, the way I wrote this, if CSS is turned off in the browser, there will be no lightsaber to show. Pay attention to the id’s I used, as these will be critical in the CSS.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script> <script src="scripts.js" type="text/javascript"></script> </body> </html>
At the very end of my document I reference Google’s hosted version of jQuery, as well as our own scripts.js file. We are now ready for the CSS!

CSS
* { margin: 0; padding: 0; border: 0; outline: 0; font-weight: normal; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; text-decoration: none; -webkit-font-smoothing: antialiased; }
:focus { outline: 0; }
body { line-height: 1; color: black; background: white; }
ol, ul { list-style: none; }
table { border-collapse: separate; border-spacing: 0; }
caption, th, td { text-align: left; font-weight: normal; }
blockquote:before, blockquote:after, q:before, q:after { content: ""; }
blockquote, q { quotes: "" ""; }
The first thing I did was paste in a simple CSS reset. This is critical in making sure that all our elements are placed in their correct position.
@font-face {
font-family: 'EPISODE-I';
src: url('episode1-webfont.eot');
src: local('☺'), url('episode1-webfont.woff') format('woff'), url('episode1-webfont.ttf') format('truetype'), url('episode1-webfont.svg#webfont9vphcQu2') format('svg');
font-weight: normal;
font-style: normal;
}
This is a simple bit of @font-face code to embed the wonderful font, Episode I by Boba Fonts that I used for the text above the lightsaber.
body {
background-color: #000;
}
Basic, defining the body’s background-color.
h1 {
color: #ecc760;
font-size: 40px;
font-family: EPISODE-I, Helvetica, sans-serif;
text-align: center;
margin-top: 30px;
word-spacing: 10px;
line-height: 50px;
text-shadow: 0px 0px 30px rgba(233,199,96,.5);
}
I now applied that embedded font to all h1’s on the page, as well as defined it’s color, font size, text align, margin, word-spacing, line-height, and text-shadow. Nothing to amazing going on here, with the possible exception of the text-shadow. The color is using a property called rgba to define a color as well as its transparency.
![]()
Before I move forward I should talk a little bit about my process when creating this. I started out by drawing the lightsaber in Photoshop using basic shapes, colors, and gradients. Realistically, for something like this, you don’t have to start in Photoshop, but it helps me visualize the result of the code beforehand.
I then wrote the markup and styled the elements’ colors, gradients, shapes, sizes, etc. before moving them into place. This is just how I like to work, and it is the way I will be doing it in this tutorial.
#container {
width: 50px;
height: auto;
margin: 0 auto;
margin-top: 15px;
}
To start off on this stage, I’m going to define the container div’s width and height, which then allows me to write “margin: 0 auto;” to align the div to the center.
#blade {
background-color: #FFF;
width: 20px;
height: 500px;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
Next I’ll give the blade a width, height, background-color of white, as well as the three lines necessary to insure a border-radius on all modern browsers. You’ll notice that the border-radius is set to half the width of the element, this is actually the maximum value you can put in, as it is the limit of how round the edge can be.
Next we add the lines,
-webkit-box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4); box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4); -moz-box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4);
This is where it starts getting complex, in the glow. Don’t be intimidated by the many numbers, as each line is basically the same as the line before it. What do I mean by that? Well, to make sure the glow renders correctly in most browsers, I essentially have to repeat the code three times with the same syntax but different names.
What the code is saying is to have eight – that’s right, eight – box-shadows with the same color but varying opacity to provide the illusion of the blade glowing. Each shadow is separated by a comma and a space. Once again I’m using the RGBA property to have all the shadows have the same color but different opacity.
If we preview what we have now we’ll come up with:

Pretty awesome right!? Well we still have to make the hilt so let’s keep going.
#hiltMain {
width: 24px;
height: 137px;
}
To start of the hilt we can style the main hilt body, first by defining its width and height.
Now the fun part! To add the gradient background, add the following lines to #hiltMain:
background: rgb(233,233,233); background: -webkit-gradient( linear, left bottom, right bottom, color-stop(0, rgb(209,209,209)), color-stop(0.5, rgb(233,233,233)), color-stop(1, rgb(209,209,209)) ); background: -moz-linear-gradient( right center, rgb(209,209,209) 0%, rgb(233,233,233) 50%, rgb(209,209,209) 100% );
Okay, okay, calm down. I know it’s scary but I’m going to walk you through it step by step.
The first line is simply a fallback for the background color if the browser it is being viewed in doesn’t support gradients. It’s doing this with by telling the browser the color’s red, green, and blue values.
The lines 2-9 are the code for a gradient in webkit browsers (Chrome and Safari). Line 3 tells what kind of gradient it is, in this case linear. Lines 4-5 tells the browser where the gradient starts and ends, in that order. For this gradient, it starts on the bottom left and ends on the bottom right.
Now we come to the bulk of the code, lines 5-7, which are the color stops in the gradient. The first one is at the point 0, which is the start, while the second one is at point 0.5, which is the middle. The last one is at 1, the end of the gradient. It’s important to note that in the webkit code, it goes from 0 to 1, as opposed to 0 – 100 in the Mozilla code.
One more step before moving on: the Mozilla code. As you can see, it’s a lot shorter than the webkit code, so let’s jump right in. The property itself, -moz-linear-gradient, already incorporates one of the lines of webkit, the gradient type. From there we simply declare the starting point, and then the color stops. An important thing to note here is that the color stops are declared with percents, from 1 to 100, as opposed to from 0 to 1.
Phew. Now we’ve got that out of the way it’s time for me to confess something: I didn’t write this code. Sure, I plugged in my own values, but the actual code was generated by an excellent tool, the CSS3 Gradient Generator. This is a great website to have in your bookmarks, as well as a great tool to have in your toolbox.
Moving right along here, if you preview what we have at this point, you should come up with something like this:

We’re certainly getting there, but we’ve still got a ways to go before I’m happy with it.
Next I’m going to style the opening at the top of the hilt.
#opening {
width: 24px;
height: 24px;
background-color: #646262;
box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
-webkit-box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
-moz-box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
border-radius: 12px;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
}
You should start to notice a pattern here. First I set its width and height, then background color. Next I applied an inner shadow to the element by using the three lines for box-shadows with a little twist. Before the values of x, y, blur, and color, I put the word “inset,” and it will applied to the inside of the element. Lastly I turned the shape into a perfect circle by setting its border-radius property to half its width and height.
The end of the hilt is a pretty simple procedure as well.
#end {
width: 24px;
height: 24px;
border-radius: 12px;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
background: rgb(233,233,233);
background: -webkit-gradient(
linear,
right bottom,
left bottom,
color-stop(0, rgb(209,209,209)),
color-stop(0.5, rgb(233,233,233)),
color-stop(1, rgb(209,209,209))
);
background: -moz-linear-gradient(
right center,
rgb(209,209,209) 0%,
rgb(233,233,233) 50%,
rgb(209,209,209) 100%
);
}
This is basically the same thing as above, except instead of the inner shadow, I pasted in the gradient code from before so it’s the same color as the main hilt shape.

Now I’m going to work on the details of the hilt, specifically the four black bars at the bottom. To start I’m going to quickly revisit the html structure of the bars so you can understand why I’m styling them like I am.
<div id="bars">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div><!--end bars-->
There is a class applied to each individual div, “bar,” and a class applied to the middle ones, “middle” so we can move them down a few pixels like the example. That whole structure is wrapped with a div with an id of “bars,” and that is what’s going to be moved into place.
.bar {
width: 6px;
height: 43px;
float: left;
margin-right: 1px;
background-color: #1c1c1c;
border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
}
First I give each on a width and height, then I float each one left so they are side by side. Next I gave them a background color and 2px border radius.
.middle {
margin-top: 3px;
}
This code moves the two bars in the middle down three pixels. Nothing fancy going on here.

The next detail I’m going to add is a thin line running down the middle of the hilt. This is perhaps the simplest detail with only three properties assigned.
#line {
width: 1px;
height: 90px;
background-color: #676767;
}
Just two more things before we’re ready to position, the green switch, and the circles.
Once again quite a simple detail, the switch needs only three properties.
#switch {
width: 7px;
height: 23px;
background-color: #007300;
}
Moving right along, the circles are actually quite similar to the bars in structure.
.circle {
width: 4px;
height: 4px;
border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
background-color: #545454;
margin-bottom: 2px;
}

Alright! Give yourselves a pat on the back. We’re ready to position! We are going to move the elements into place using absolute positioning, which will remove the element from normal document flow.
To start we have to add this line to the #container rule:
position: relative;
This will allow us to position the other elements relative to the container.
Next we have to add these two lines to the #blade section:
position: absolute; z-index: 10;
This will remove it from the document flow and move it above all other absolutely positioned elements.
We’ll move the first element, the main hilt shape, into place now.
position: absolute; left: -2px; top: 491px;
The “left: -2px;” bit is compensating for the fact that the hilt is 4 pixels wider than the blade core.
To move the hilt opening into place we’ll add these lines:
position: absolute; z-index: 1; left: -2px; top: 480px;
Nothing peculiar about that, just that I set the z-index to 1 so it will appear above the main hilt shape.
The end of the hilt is positioned much the same way.
position: absolute; z-index: -2; left: -2px; top: 617px;
The only difference is that I set the z-index to a negative number so it appears beneath the main hilt shape.
#bars {
position: absolute;
z-index: 5;
top: 593px;
left: -3px;
}
This code will position the bars correctly and above the hilt background.
Position the thin line:
position: absolute; z-index: 11; left: 10px; top: 504px;
And the switch:
position: absolute; z-index: 6; left: 10px; top: 560px;
Finally the circles are positioned:
#circles {
position: absolute;
z-index: 6;
top: 510px;
left: 2px;
}
We’re basically done here, there’s just one more thing I’d like to do, which is to rotate the lightsaber slightly. This is done by adding these lines to the #container rule:
transform: rotate(-15deg); -webkit-transform: rotate(-15deg); -moz-transform: rotate(-15deg);
…and finished!

The whole CSS file now contains all of this:
* { margin: 0; padding: 0; border: 0; outline: 0; font-weight: normal; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; text-decoration: none; -webkit-font-smoothing: antialiased; }
:focus { outline: 0; }
body { line-height: 1; color: black; background: white; }
ol, ul { list-style: none; }
table { border-collapse: separate; border-spacing: 0; }
caption, th, td { text-align: left; font-weight: normal; }
blockquote:before, blockquote:after, q:before, q:after { content: ""; }
blockquote, q { quotes: "" ""; }
body {
background-color: #000;
}
h1 {
color: #ecc760;
font-size: 40px;
font-family: EPISODE-I, Helvetica, sans-serif;
text-align: center;
margin-top: 30px;
word-spacing: 10px;
line-height: 50px;
text-shadow: 0px 0px 30px rgba(233,199,96,.5);
}
#container {
width: 50px;
height: auto;
margin: 0 auto;
margin-top: 15px;
position: relative;
transform: rotate(-15deg);
-webkit-transform: rotate(-15deg);
-moz-transform: rotate(-15deg);
}
#blade {
position: absolute;
z-index: 10;
background-color: #FFF;
width: 20px;
height: 500px;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-webkit-box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4);
box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4);
-moz-box-shadow: 0px 0px 5px rgba(255,255,255,1), 0px 0px 10px rgba(6,255,0,1), 0px 0px 15px rgba(6,255,0,.8), 0px 0px 20px rgba(6,255,0,.5), 0px 0px 50px rgba(6,255,0,.5), 0px 0px 100px rgba(6,255,0,.4), 0px 0px 500px rgba(6,255,0,.7), 0px 0px 1000px rgba(6,255,0,.4);
}
#opening {
position: absolute;
z-index: 1;
left: -2px;
top: 480px;
width: 24px;
height: 24px;
background-color: #646262;
box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
-webkit-box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
-moz-box-shadow: inset 0px 0px 15px rgba(0,0,0,.5);
border-radius: 12px;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
}
#circles {
position: absolute;
z-index: 6;
top: 510px;
left: 2px;
}
.circle {
width: 4px;
height: 4px;
border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
background-color: #545454;
margin-bottom: 2px;
}
#switch {
position: absolute;
z-index: 6;
left: 10px;
top: 560px;
width: 7px;
height: 23px;
background-color: #007300;
}
#line {
width: 1px;
height: 90px;
background-color: #676767;
position: absolute;
z-index: 6;
left: 10px;
top: 504px;
}
#hiltMain {
position: absolute;
left: -2px;
top: 491px;
width: 24px;
height: 137px;
background: rgb(233,233,233);
background: -webkit-gradient(
linear,
left bottom,
right bottom,
color-stop(0, rgb(209,209,209)),
color-stop(0.5, rgb(233,233,233)),
color-stop(1, rgb(209,209,209))
);
background: -moz-linear-gradient(
right center,
rgb(209,209,209) 0%,
rgb(233,233,233) 50%,
rgb(209,209,209) 100%
);
}
#end {
position: absolute;
z-index: -2;
left: -2px;
top: 617px;
width: 24px;
height: 24px;
border-radius: 12px;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
background: rgb(233,233,233);
background: -webkit-gradient(
linear,
right bottom,
left bottom,
color-stop(0, rgb(209,209,209)),
color-stop(0.5, rgb(233,233,233)),
color-stop(1, rgb(209,209,209))
);
background: -moz-linear-gradient(
right center,
rgb(209,209,209) 0%,
rgb(233,233,233) 50%,
rgb(209,209,209) 100%
);
}
#bars {
position: absolute;
z-index: 5;
top: 593px;
left: -3px;
}
.bar {
width: 6px;
height: 43px;
float: left;
margin-right: 1px;
background-color: #1c1c1c;
border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
}
.middle {
margin-top: 3px;
}
@font-face {
font-family: 'EPISODE-I';
src: url('episode1-webfont.eot');
src: local('☺'), url('episode1-webfont.woff') format('woff'), url('episode1-webfont.ttf') format('truetype'), url('episode1-webfont.svg#webfont9vphcQu2') format('svg');
font-weight: normal;
font-style: normal;
}
jQuery
Hopefully you’re happy with this result, but if you want to take it a step further, read on to learn how to make this lightsaber a little bit more interactive
Before I get into this I just want to say this about jQuery and Javascript. If you’re going to add interactivity, you should try to make sure that if a user has Javascript turned off they’re still going to be able to access your content. This is what I’m going to do in this tutorial and what you should try to do in your own projects.
The effect we will be making today is to have the blade of the lightsaber expand from nothing when a link is clicked.
Remember way back in the beginning of this tutorial when I mentioned that you should pay special attention to the nesting around the “lightsaber” wording in the h1 element? Well there was a reason for that, and here it is. What’s going to happen is that when the page is loaded, that span tag we wrapped “lightsaber” in, is going to be wrapped in an anchor tag. When that anchor is clicked, the lightsaber will expand and the anchor tag will remove itself from the span tag, preventing further clicks.
To start, make a file called “scripts.js” in the root of your document and paste in the following code:
$(document).ready(function(){
...
});
This will execute whatever is between the two main lines once the page is loaded.
Next, add these lines on line 2:
$('#blade').css('height', '0px');
$('#blade').css('top', '500px');
This will change the blades height to 0px and move it down to the hilt when the page is loaded. Like I mentioned before, this means that if a user has Javascript turned off, the lightsaber will appear there, but if they have Javascript enabled, the blade will hide itself and wait to be revealed.
Now add this line underneath those:
$('span').wrap('');
Now when the document is loaded, any span tags will be wrapped in an anchor tag with an id of “start.” This wouldn’t work if I had multiple span tags on the page, but since there is only one, it is okay.
Here comes the meat of the code:
$('#start').click(function(event) {
$('#blade').animate({height : '500px', top : '-=500px'}, 400);
$('span').unwrap();
});
Now, when the element with an id of “start” is clicked, the blade will animate in, and the anchor tag around the span tag will be removed.
The last thing I want to do is style the anchor tag with these lines:
a {
color: #FFF;
text-shadow: 0px 0px 30px rgba(255,255,255,1);
-webkit-transition: text-shadow .2s ease-out;
}
a:hover {
text-shadow: 0px 0px 35px rgba(255,255,255,.5);
}
On line 4 you can see I’m using a -webkit-transition to animate its text shadow on hover. The syntax is very easy to learn, basically it goes the property you want to animate, then the time you want it to take, then finally the transition you want to use. You can check out the official documentation for all the details.
Congratulations! If you preview this in a browser now it should be looking pretty nice.
If you’ve made it to the end then I commend you. This was not a short tutorial.



hehe.. nice tut. thanks
Hah this is awesome. Thanks for posting..
Great article, very creative use of the new CSS properties! Thanks for posting, keep up the good work.
Hola,
http://www.thedesigngnome.com – da mejor. Guardar va!
Gracias
Dolly
Hola,
їPuedo tomar obtener una pequeсa foto de su blog?
Socco