[css] Outline effect to text

Are there any ways in CSS to give outlines to text with different colors ? I want to highlight some parts of my text to make it more intuitive - like the names, links, etc. Changing the link colors etc. are common nowadays, so I want something new.

This question is related to css

The answer is


I was looking for a cross-browser text-stroke solution that works when overlaid on background images. think I have a solution for this that doesn't involve extra mark-up, js and works in IE7-9 (I haven't tested 6), and doesn't cause aliasing problems.

This is a combination of using CSS3 text-shadow, which has good support except IE (http://caniuse.com/#search=text-shadow), then using a combination of filters for IE. CSS3 text-stroke support is poor at the moment.

IE Filters

The glow filter (http://www.impressivewebs.com/css3-text-shadow-ie/) looks terrible, so I didn't use that.

David Hewitt's answer involved adding dropshadow filters in a combination of directions. ClearType is then removed unfortunately so we end up with badly aliased text.

I then combined some of the elements suggested on useragentman with the dropshadow filters.

Putting it together

This example would be black text with a white stroke. I'm using conditional html classes by the way to target IE (http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/).

#myelement {
    color: #000000;
    text-shadow:
    -1px -1px 0 #ffffff,  
    1px -1px 0 #ffffff,
    -1px 1px 0 #ffffff,
    1px 1px 0 #ffffff;
}

html.ie7 #myelement,
html.ie8 #myelement,
html.ie9 #myelement {
    background-color: white;
    filter: progid:DXImageTransform.Microsoft.Chroma(color='white') progid:DXImageTransform.Microsoft.Alpha(opacity=100) progid:DXImageTransform.Microsoft.dropshadow(color=#ffffff,offX=1,offY=1) progid:DXImageTransform.Microsoft.dropshadow(color=#ffffff,offX=-1,offY=1) progid:DXImageTransform.Microsoft.dropshadow(color=#ffffff,offX=1,offY=-1) progid:DXImageTransform.Microsoft.dropshadow(color=#ffffff,offX=-1,offY=-1);
    zoom: 1;
}

Edit: text-stroke is now fairly mature and implemented in most browsers. It is easier, sharper and more precise. If your browser audience can support it, you should now use text-stroke first, instead of text-shadow.


You can and should do this with just the text-shadow effect without any offsets:

.outline {
    color: #fff;
    text-shadow: #000 0px 0px 1px;
    -webkit-font-smoothing: antialiased;
}

Why? When you offset multiple shadow effects, you’ll begin to notice ungainly, jagged corners: Shadow effect offsets result in noticeable jagged corners.


Having text-shadow effects in just one position eliminates the offsets, meaning If you feel that’s too thin and would prefer a darker outline instead, no problem — you can repeat the same effect (keeping the same position and blur), several times. Like so:

text-shadow: #000 0px 0px 1px,   #000 0px 0px 1px,   #000 0px 0px 1px,
             #000 0px 0px 1px,   #000 0px 0px 1px,   #000 0px 0px 1px;

Here’s a sample of just one effect (top), and the same effect repeated 14 times (bottom):


Sample text rendered with text-shadow

Also note: Because the lines become so thin, it’s a very good idea to turn off sub-pixel rendering using
-webkit-font-smoothing: antialiased.


Multiple text-shadows..
Something like this:

var steps = 10,
    i,
    R = 0.6,
    x,
    y,
    theStyle = '1vw 1vw 3vw #005dab';
for (i = -steps; i <= steps; i += 1) {
    x = (i / steps) / 2;
    y = Math.sqrt(Math.pow(R, 2) - Math.pow(x, 2));
    theStyle = theStyle + ',' + x.toString() + 'vw ' + y.toString() + 'vw 0 #005dab';
    theStyle = theStyle + ',' + x.toString() + 'vw -' + y.toString() + 'vw 0 #005dab';
    theStyle = theStyle + ',' + y.toString() + 'vw ' + x.toString() + 'vw 0 #005dab';
    theStyle = theStyle + ',-' + y.toString() + 'vw ' + x.toString() + 'vw 0 #005dab';
}
document.getElementsByTagName("H1")[0].setAttribute("style", "text-shadow:" + theStyle);

Demo: http://jsfiddle.net/punosound/gv6zs58m/


Just adding this answer. "Stroking" the text is not the same as "Outlining"

Outlining looks great. Stroking looks horrid.

The SVG solution listed elsewhere has the same issue. Of you want an outline you need to put the text twice. Once stroked and again not stroked.

Stroking IS NOT Outlining

_x000D_
_x000D_
body {_x000D_
  font-family: sans-serif;_x000D_
  margin: 20px;_x000D_
}_x000D_
_x000D_
.stroked {_x000D_
  color: white;_x000D_
  -webkit-text-stroke: 1px black;_x000D_
}_x000D_
_x000D_
.thickStroked {_x000D_
  color: white;_x000D_
  -webkit-text-stroke: 10px black;_x000D_
}_x000D_
_x000D_
.outlined {_x000D_
  color: white;_x000D_
  text-shadow:_x000D_
    -1px -1px 0 #000,_x000D_
     0   -1px 0 #000,_x000D_
     1px -1px 0 #000,_x000D_
     1px  0   0 #000,_x000D_
     1px  1px 0 #000,_x000D_
     0    1px 0 #000,_x000D_
    -1px  1px 0 #000,_x000D_
    -1px  0   0 #000;_x000D_
}_x000D_
_x000D_
.thickOutlined {_x000D_
  color: white;_x000D_
text-shadow: 0.0px 10.0px 0.02px #000, 9.8px 2.1px 0.02px #000, 4.2px -9.1px 0.02px #000, -8.0px -6.0px 0.02px #000, -7.6px 6.5px 0.02px #000, 4.8px 8.8px 0.02px #000, 9.6px -2.8px 0.02px #000, -0.7px -10.0px 0.02px #000, -9.9px -1.5px 0.02px #000, -3.5px 9.4px 0.02px #000, 8.4px 5.4px 0.02px #000, 7.1px -7.0px 0.02px #000, -5.4px -8.4px 0.02px #000, -9.4px 3.5px 0.02px #000, 1.4px 9.9px 0.02px #000, 10.0px 0.8px 0.02px #000, 2.9px -9.6px 0.02px #000, -8.7px -4.8px 0.02px #000, -6.6px 7.5px 0.02px #000, 5.9px 8.0px 0.02px #000, 9.1px -4.1px 0.02px #000, -2.1px -9.8px 0.02px #000, -10.0px -0.1px 0.02px #000, -2.2px 9.8px 0.02px #000, 9.1px 4.2px 0.02px #000, 6.1px -8.0px 0.02px #000, -6.5px -7.6px 0.02px #000, -8.8px 4.7px 0.02px #000, 2.7px 9.6px 0.02px #000, 10.0px -0.6px 0.02px #000, 1.5px -9.9px 0.02px #000, -9.3px -3.6px 0.02px #000, -5.5px 8.4px 0.02px #000, 7.0px 7.2px 0.02px #000, 8.5px -5.3px 0.02px #000, -3.4px -9.4px 0.02px #000, -9.9px 1.3px 0.02px #000, -0.8px 10.0px 0.02px #000, 9.6px 2.9px 0.02px #000, 4.9px -8.7px 0.02px #000, -7.5px -6.7px 0.02px #000, -8.1px 5.9px 0.02px #000, 4.0px 9.2px 0.02px #000, 9.8px -2.0px 0.02px #000, 0.2px -10.0px 0.02px #000, -9.7px -2.3px 0.02px #000, -4.3px 9.0px 0.02px #000, 7.9px 6.1px 0.02px #000_x000D_
}_x000D_
_x000D_
svg {_x000D_
  font-size: 40px;_x000D_
  font-weight: bold;_x000D_
  width: 450px;_x000D_
  height: 70px;_x000D_
  fill: white;_x000D_
}_x000D_
_x000D_
.svgStroke {_x000D_
  fill: white;_x000D_
  stroke: black;_x000D_
  stroke-width: 20px;_x000D_
  stroke-linejoin: round;_x000D_
}
_x000D_
<h1 class="stroked">Properly stroked!</h1>_x000D_
<h1 class="outlined">Properly outlined!</h1>_x000D_
_x000D_
<h1 class="thickStroked">Thickly stroked!</h1>_x000D_
<h1 class="thickOutlined">Thickly outlined!</h1>_x000D_
_x000D_
<svg viewBox="0 0 450 70">_x000D_
  <text class="svgStroke" x="10" y="45">SVG Thickly Stroked!</text>_x000D_
</svg>_x000D_
<svg viewBox="0 0 450 70">_x000D_
  <text class="svgStroke" x="10" y="45">SVG Thickly Outlined!</text>_x000D_
  <text class="svgText" x="10" y="45">SVG Thickly Outlined!</text>_x000D_
</svg>
_x000D_
_x000D_
_x000D_

PS: I'd love to know how to make the SVG be the correct size of any arbitrary text. I have a feeling it's fairly complicated involving generating the svg, querying it with javascript to get the extents then applying the results. If there is an easier non-js way I'd love to know.


You could try stacking multiple blured shadows until the shadows look like a stroke, like so:

.shadowOutline {
  text-shadow: 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black;
}

Here's a fiddle: http://jsfiddle.net/GGUYY/

I mention it just in case someone's interested, although I wouldn't call it a solution because it fails in various ways:

  • it doesn't work in old IE
  • it renders quite differently in every browser
  • applying so many shadows is very heavy to process :S

Working with thicker strokes gets a bit messy, if you have the pleasure of sass try this mixin, not perfect and depending on stroke weight it generates a fair amount of css.

 @mixin stroke($width, $colour: #000000) {
      $shadow: 0 0 0 $colour; // doesn't do anything but I couldn't work out how to create a blank string and maintain commas
      @for $i from 0 through $width {
          $shadow: $shadow,
          -$i + px -$width + px 0 $colour,
          $i + px -$width + px 0 $colour,
          -$i + px $width + px 0 $colour,
          $i + px $width + px 0 $colour,
          -$width + px -$i + px 0 $colour,
          $width + px -$i + px 0 $colour,
          -$width + px $i + px 0 $colour,
          $width + px $i + px 0 $colour,
      }
      text-shadow: $shadow;
}

Text Shadow Generator

There are lots of great answers here. It would appear the text-shadow is probably the most reliable way to do this. I won't go into detail about how to do this with text-shadow since others have already done that, but the basic idea is that you create multiple text shadows around the text element. The larger the text outline, the more text shadows you need.

All of the answers submitted (as of this posting) provide static solutions for the text-shadow. I have taken a different approach and have written this JSFiddle that accepts outline color, blur, and width values as inputs and generates the appropriate text-shadow property for your element. Just fill in the blanks, check the preview, and click to copy the css and paste it into your stylesheet.


Needless Appendix

Apparently, answers that include a link to a JSFiddle cannot be posted unless they also contain code. You can completely ignore this appendix if you want to. This is the JavaScript from my fiddle that generates the text-shadow property. Please note that you do not need to implement this code in your own works:

function computeStyle() {
    var width = document.querySelector('#outline-width').value;
  width = (width === '') ? 0 : Number.parseFloat(width);
  var blur = document.querySelector('#outline-blur').value;
  blur = (blur === '') ? 0 : Number.parseFloat(blur);
  var color = document.querySelector('#outline-color').value;
  if (width < 1 || color === '') {
    document.querySelector('.css-property').innerText = '';
    return;
  }
    var style = 'text-shadow: ';
  var indent = false;
    for (var i = -1 * width; i <= width; ++i) {
    for (var j = -1 * width; j <= width; ++j) {
        if (! (i === 0 && j === 0 && blur === 0)) {
        var indentation = (indent) ? '\u00a0\u00a0\u00a0\u00a0' : '';
            style += indentation + i + "px " + j + "px " + blur + "px " + color + ',\n';
        indent = true;
      }
    }
  }
  style = style.substring(0, style.length - 2) + '\n;';
  document.querySelector('.css-property').innerText = style;

  var exampleBackground = document.querySelector('#example-bg');
  var exampleText = document.querySelector('#example-text');
  exampleBackground.style.backgroundColor = getOppositeColor(color);
  exampleText.style.color = getOppositeColor(color);
  var textShadow = style.replace(/text-shadow: /, '').replace(/\n/g, '').replace(/.$/, '').replace(/\u00a0\u00a0\u00a0\u00a0/g, '');
  exampleText.style.textShadow = textShadow;
}

Here is CSS file hope you will get wht u want

/* ----- Logo ----- */

#logo a {
    background-image:url('../images/wflogo.png'); 
    min-height:0;
    height:40px;
    }
* html #logo a {/* IE6 png Support */
    background-image: none;
    filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../images/wflogo.png", sizingMethod="crop");
}

/* ----- Backgrounds ----- */
html{
    background-image:none;  background-color:#336699;
}
#logo{
    background-image:none;  background-color:#6699cc;
}
#container, html.embed{
    background-color:#FFFFFF;
}
.safari .wufoo input.file{
    background:none;
    border:none;
}

.wufoo li.focused{
    background-color:#FFF7C0;
}
.wufoo .instruct{
    background-color:#F5F5F5;
}

/* ----- Borders ----- */
#container{
    border:0 solid #cccccc;
}
.wufoo .info, .wufoo .paging-context{
    border-bottom:1px dotted #CCCCCC;
}
.wufoo .section h3, .wufoo .captcha, #payment .paging-context{
    border-top:1px dotted #CCCCCC;
}
.wufoo input.text, .wufoo textarea.textarea{

}
.wufoo .instruct{
    border:1px solid #E6E6E6;
}
.fixed .info{
    border-bottom:none;
}
.wufoo li.section.scrollText{
    border-color:#dedede;
}

/* ----- Typography ----- */
.wufoo .info h2{
    font-size:160%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#000000;
}
.wufoo .info div{
    font-size:95%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#444444;
}
.wufoo .section h3{
    font-size:110%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#000000;
}
.wufoo .section div{
    font-size:85%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#444444;
}

.wufoo label.desc, .wufoo legend.desc{
    font-size:95%;
    font-family:inherit;
    font-style:normal;
    font-weight:bold;
    color:#444444;
}

.wufoo label.choice{
    font-size:100%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#444444;
}
.wufoo input.text, .wufoo textarea.textarea, .wufoo input.file, .wufoo select.select{
    font-style:normal;
    font-weight:normal;
    color:#333333;
    font-size:100%;
}
{* Custom Fonts Break Dropdown Selection in IE *}
.wufoo input.text, .wufoo textarea.textarea, .wufoo input.file{ 
    font-family:inherit;
}


.wufoo li div, .wufoo li span, .wufoo li div label, .wufoo li span label{
    font-family:inherit;
    color:#444444;
}
.safari .wufoo input.file{ /* Webkit */
    font-size:100%;
    font-family:inherit;
    color:#444444;
}
.wufoo .instruct small{
    font-size:80%;
    font-family:inherit;
    font-style:normal;
    font-weight:normal;
    color:#444444;
}

.altInstruct small, li.leftHalf small, li.rightHalf small,
li.leftThird small, li.middleThird small, li.rightThird small,
.iphone small{
    color:#444444 !important;
}

/* ----- Button Styles ----- */

.wufoo input.btTxt{

}

/* ----- Highlight Styles ----- */

.wufoo li.focused label.desc, .wufoo li.focused legend.desc,
.wufoo li.focused div, .wufoo li.focused span, .wufoo li.focused div label, .wufoo li.focused span label,
.safari .wufoo li.focused input.file{ 
    color:#000000;
}

/* ----- Confirmation ----- */

.confirm h2{
    font-family:inherit;
    color:#444444;
}
a.powertiny b, a.powertiny em{
    color:#1a1a1a !important;
}
.embed a.powertiny b, .embed a.powertiny em{
    color:#1a1a1a !important;
}

/* ----- Pagination ----- */

.pgStyle1 var, .pgStyle2 var, .pgStyle2 em, .page1 .pgStyle2 var, .pgStyle1 b, .wufoo .buttons .marker{
    font-family:inherit;
    color:#444444;
}
.pgStyle1 var, .pgStyle2 td{
    border:1px solid #cccccc;
}
.pgStyle1 .done var{
    background:#cccccc;
}

.pgStyle1 .selected var, .pgStyle2 var, .pgStyle2 var em{
    background:#FFF7C0;
    color:#000000;
}
.pgStyle1 .selected var{
    border:1px solid #e6dead;
}


/* Likert Backgrounds */

.likert table{
    background-color:#FFFFFF;
}
.likert thead td, .likert thead th{
    background-color:#e6e6e6;
}
.likert tbody tr.alt td, .likert tbody tr.alt th{
    background-color:#f5f5f5;
}

/* Likert Borders */

.likert table, .likert th, .likert td{
    border-color:#dedede;
}
.likert td{
    border-left:1px solid #cccccc;
}

/* Likert Typography */

.likert caption, .likert thead td, .likert tbody th label{
    color:#444444;
    font-family:inherit;
}
.likert tbody td label{
    color:#575757;
    font-family:inherit;
}
.likert caption, .likert tbody th label{
    font-size:95%;
}

/* Likert Hover */

.likert tbody tr:hover td, .likert tbody tr:hover th, .likert tbody tr:hover label{
    background-color:#FFF7C0;
    color:#000000;
}
.likert tbody tr:hover td{
    border-left:1px solid #ccc69a;
}

/* ----- Running Total ----- */

.wufoo #lola{
    background:#e6e6e6;
}
.wufoo #lola tbody td{
    border-bottom:1px solid #cccccc;
}
.wufoo #lola{
    font-family:inherit;
    color:#444444;
}
.wufoo #lola tfoot th{
    color:#696969;
}

/* ----- Report Styles ----- */

.wufoo .wfo_graph h3{
    font-size:95%;
    font-family:inherit;
    color:#444444;
}
.wfo_txt, .wfo_graph h4{
    color:#444444;
}
.wufoo .footer h4{
    color:#000000;
}
.wufoo .footer span{
    color:#444444;
}

/* ----- Number Widget ----- */

.wfo_number{
    background-color:#f5f5f5;
    border-color:#dedede;
}
.wfo_number strong, .wfo_number em{
    color:#000000;
}

/* ----- Chart Widget Border and Background Colors ----- */

#widget, #widget body{
    background:#FFFFFF;
}
.fcNav a.show{
    background-color:#FFFFFF;
    border-color:#cccccc;
}
.fc table{
    border-left:1px solid #dedede;  
}
.fc thead th, .fc .more th{
    background-color:#dedede !important;
    border-right:1px solid #cccccc !important;
}
.fc tbody td, .fc tbody th, .fc tfoot th, .fc tfoot td{
    background-color:#FFFFFF;
    border-right:1px solid #cccccc;
    border-bottom:1px solid #dedede;
}
.fc tbody tr.alt td, .fc tbody tr.alt th, .fc tbody td.alt{
    background-color:#f5f5f5;
}

/* ----- Chart Widget Typography Colors ----- */

.fc caption, .fcNav, .fcNav a{
    color:#444444;
}
.fc tfoot, 
.fc thead th,
.fc tbody th div, 
.fc tbody td.count, .fc .cards tbody td a, .fc td.percent var,
.fc .timestamp span{
    color:#000000;
}
.fc .indent .count{
    color:#4b4b4b;
}
.fc .cards tbody td a span{
    color:#7d7d7d;
}

/* ----- Chart Widget Hover Colors ----- */

.fc tbody tr:hover td, .fc tbody tr:hover th,
.fc tfoot tr:hover td, .fc tfoot tr:hover th{
    background-color:#FFF7C0;
}
.fc tbody tr:hover th div, .fc tbody tr:hover td, .fc tbody tr:hover var,
.fc tfoot tr:hover th div, .fc tfoot tr:hover td, .fc tfoot tr:hover var{
    color:#000000;
}

/* ----- Payment Summary ----- */

.invoice thead th, 
.invoice tbody th, .invoice tbody td,
.invoice tfoot th,
.invoice .total,
.invoice tfoot .last th, .invoice tfoot .last td,
.invoice tfoot th, .invoice tfoot td{
    border-color:#dedede;
}
.invoice thead th, .wufoo .checkNotice{
    background:#f5f5f5;
}
.invoice th, .invoice td{
    color:#000000;
}
#ppSection, #ccSection{
    border-bottom:1px dotted #CCCCCC;
}
#shipSection, #invoiceSection{
    border-top:1px dotted #CCCCCC;
}

/* Drop Shadows */

/* - - - Local Fonts - - - */

/* - - - Responsive - - - */

@media only screen and (max-width: 480px) {
    html{
        background-color:#FFFFFF;
    }
    a.powertiny b, a.powertin em{
        color:#1a1a1a !important;
    }
}

/* - - - Custom Theme - - - */

I had this issue as well, and the text-shadow wasn't an option because the corners would look bad (unless I had many many shadows), and I didn't want any blur, therefore my only other option was to do the following: Have 2 divs, and for the background div, put a -webkit-text-stroke on it, which then allows for as big of an outline as you like.

_x000D_
_x000D_
div {_x000D_
  font-size: 200px;_x000D_
  position: absolute;_x000D_
  white-space: nowrap;_x000D_
}_x000D_
_x000D_
.front {_x000D_
 color: blue;_x000D_
}_x000D_
_x000D_
.outline {_x000D_
  -webkit-text-stroke: 30px red;_x000D_
  user-select: none;_x000D_
}
_x000D_
<div class="outline">_x000D_
 outline text_x000D_
</div>_x000D_
_x000D_
<div class="front">_x000D_
 outline text_x000D_
</div>  
_x000D_
_x000D_
_x000D_

Using this, I was able to achieve an outline, because the stroke-width method was not an option if you want your text to remain legible with a very large outline (because with stroke-width the outline will start inside the lettering which makes it not legible when the width gets larger than the letters.

Note: the reason I needed such a fat outline was I was emulating the street labels in "google maps" and I wanted a fat white halo around the text. This solution worked perfectly for me.

Here is a fiddle showing this solution

enter image description here


_x000D_
_x000D_
h1 {_x000D_
   color: black;_x000D_
   -webkit-text-fill-color: white; /* Will override color (regardless of order) */_x000D_
   -webkit-text-stroke-width: 1px;_x000D_
   -webkit-text-stroke-color: black;_x000D_
}
_x000D_
<h1>Properly stroked!</h1>
_x000D_
_x000D_
_x000D_


I got better results with 6 different shadows:

.strokeThis{
    text-shadow:
    -1px -1px 0 #ff0,
    0px -1px 0 #ff0,
    1px -1px 0 #ff0,
    -1px 1px 0 #ff0,
    0px 1px 0 #ff0,
    1px 1px 0 #ff0;
}

This mixin for SASS will give smooth results, using 8-axis:

@mixin stroke($size: 1px, $color: #000) {
  text-shadow:
    -#{$size} -#{$size} 0 $color,
     0        -#{$size} 0 $color,
     #{$size} -#{$size} 0 $color,
     #{$size}  0        0 $color,
     #{$size}  #{$size} 0 $color,
     0         #{$size} 0 $color,
    -#{$size}  #{$size} 0 $color,
    -#{$size}  0        0 $color;
}

And normal CSS:

text-shadow:
  -1px -1px 0 #000,
   0   -1px 0 #000,
   1px -1px 0 #000,
   1px  0   0 #000,
   1px  1px 0 #000,
   0    1px 0 #000,
  -1px  1px 0 #000,
  -1px  0   0 #000;

Easy! SVG to the rescue.

This is a simplified method:

_x000D_
_x000D_
svg{
  font   : bold 70px Century Gothic, Arial;
  width  : 100%;
  height : 120px;
}

text{
  fill            : none;
  stroke          : black;
  stroke-width    : .5px;
  stroke-linejoin : round;
  animation       : 2s pulsate infinite;
}

@keyframes pulsate {
  50%{ stroke-width:5px }
}
_x000D_
<svg viewBox="0 0 450 50">
  <text y="50">Scalable Title</text>
</svg>
_x000D_
_x000D_
_x000D_

Here's a more complex demo.