36

I have the following HTML and CSS:

body { background-color: gray; }
h1 {
  color: white;
  font-size: 2.5em;
}
<h1>WHAT CARRER SHOULD YOU HAVE ?</h1>

Which renders like this:

enter image description here

I want to add a stroke around it, that means a black border around these text.
I Googled and found -webkit-text-stroke, and came up with:

body { background-color: gray; }
h1 {
  color: white;
  font-size: 2.5em;
  -webkit-text-stroke: 2px black;
}
<h1>WHAT CARRER SHOULD YOU HAVE ?</h1>

However, the effect is not what I want:

enter image description here

As you can see, it seems that the stroke is added inside the text, which make the text looks too thin for me.

How can I make the stroke outside the text?

Fiddle: http://jsfiddle.net/jpjbk1z7/

PS: only webkit support is needed

James McCormack
  • 9,217
  • 3
  • 47
  • 57
wong2
  • 34,358
  • 48
  • 134
  • 179
  • 1
    use text-shadow. Example : http://jsfiddle.net/lotusgodkk/jpjbk1z7/1/ – K K Oct 29 '14 at 15:24
  • 2
    I found this on google: "use the :before pseudo-element with the -webkit-text-stroke property set to create a "stroked" copy of the main text, then use z-index to put the copy behind the original, ending up with an outside stroke" http://www.petercarrero.com/examples/stroke/ – wf4 Oct 29 '14 at 15:25
  • Possible duplicate of [CSS- webkit-text-stroke but stroke covers font-color](http://stackoverflow.com/questions/17484262/css-webkit-text-stroke-but-stroke-covers-font-color) – Tobi Reif Mar 04 '17 at 16:25

14 Answers14

53

For a smooth outside stroke emulated by text shadow, use the following 8-axis shadow:

  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;

For customisation, you can use this SASS mixin instead (although changing the size does have side effects on rendering):

@mixin stroke($color: #000, $size: 1px) {
  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;
}

This gives a very smooth stroke, without missing parts, like on the 4 axis solution.

Ryall
  • 12,010
  • 11
  • 53
  • 77
  • Very cool and simple. The only thing is, I can't understand why it appears to have a slightly thicker stroke on the top than the bottom. Until I zoom in, then it looks correct. – BBaysinger Nov 24 '18 at 02:23
  • 1
    Thank you, Ryall ! Your 8-Axis "outline" rescued me when trying to emulate an outline around a subtitle text. I had tried canvas, but then found that doesn't support underlines. Using JS - with variables used to replace the user selected pixel widths - have solved the software conundrum in a couple of hours. – Cristofayre Jun 10 '20 at 11:25
  • Thanks but what if instead of "#{$" use "var(...)"? Rendering will suffer either way? – DDRRSS Dec 13 '21 at 05:36
  • It only works with 1px. Any other value will yield an unpleasant result – Federico Bellucci Apr 28 '23 at 15:27
47

Firefox and Safari now support a new CSS property called paint-order which can be used to simulate an outside stroke:

h1 {
  color: #00ff01;
  font-size: 3em;
  -webkit-text-stroke: 5px black;
}

.fix-stroke {
   paint-order: stroke fill;
}
<h1>the default often is ugly</h1>
<h1 class="fix-stroke">paint-order: stroke fill </h1>

Screenshot:

CSS paint-order screenshot

Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
25

One option is to use text-shadow to simulate a stroke. Example:

text-shadow:
    -1px -1px 0 #000,  
     1px -1px 0 #000,
    -1px  1px 0 #000,
     1px  1px 0 #000;
nunoarruda
  • 2,679
  • 5
  • 26
  • 50
13

The -webkit-text-stroke doesn't support placing the stroke on the outside of the text

as this CSS-Tricks article explains:

The stroke drawn by text-stroke is aligned to the center of the text shape (as is the default in Adobe Illustrator), and there is currently no option to set the alignment to the inside or outside of the shape. Unfortunately this makes it much less usable, as no matter what now the stroke interferes with the shape of the letter destroying the original type designers intent. A setting would be ideal, but if we had to pick one, outside stroke would have been much more useful.

What about SVG?

Well it seems that it also places the stroke on the inside -

FIDDLE

However,

you might be able to simulate this effect (depending on what you need) by:

  1. Change your font to a sans serif like verdana and

  2. Increase the font-size of the text you are adding a stroke to.

body {
  background: grey;
  font-family: verdana;
}
.stroke,
.no-stroke {
  color: white;
  font-size: 2.5em;
}
.stroke {
  -webkit-text-stroke: 2px black;
   font-size: 2.7em;
}
<h1 class="stroke">WHAT CAREER SHOULD YOU HAVE?</h1>
<h1 class="no-stroke">WHAT CAREER SHOULD YOU HAVE?</h1>
Joseph Didion
  • 142
  • 1
  • 6
Danield
  • 121,619
  • 37
  • 226
  • 255
  • 3
    It is now possible to place the stroke outside the text with [paint-order](https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order) – Felipe Cortez May 28 '18 at 15:24
  • 2
    @FelipeCortez shame it isn't supported in many browsers (e.g. Chrome) because `paint-order` would be really useful – drmrbrewer Mar 03 '19 at 12:27
11

Further elaborating on the other text-shadow solutions, for pixel-perfect thick round outlines the number of shadows should increase with the radius of the stroke. For example, a 10px thick outline requires 2⋅10⋅π ≈ 63 shadows distributed in all directions. To generate this in javascript, one could something like:

  const color = "#FFF" /* white outline */
  const r = 10 /* width of outline in pixels */
  const n = Math.ceil(2*Math.PI*r) /* number of shadows */
  var str = ''
  for(var i = 0;i<n;i++) /* append shadows in n evenly distributed directions */
  {
    const theta = 2*Math.PI*i/n
    str += (r*Math.cos(theta))+"px "+(r*Math.sin(theta))+"px 0 "+color+(i==n-1?"":",")
  }
  document.querySelector("#myoutlinedtext").style.textShadow = str

example of thick outlined text

If the thickness is static, then just run this once to generate the string and paste it into your CSS.

Alec Jacobson
  • 6,032
  • 5
  • 51
  • 88
5

One way I found was to stack two elements on each other having the element that stays behind get the double stroke width. The element behind leaks the stroke to the outside of the visible element making it a true stroke outside.

Also, another option is to use :after to create this second element. The downside is that you can't programmatically change the stroke

This might not work properly on big stroke values.

body { background-color: gray; }
h1 {
  color: white;
  font-size: 2.5em;
  font-family: verdana;
}

.stroke { -webkit-text-stroke: 2px black; }

.stack .stroke { -webkit-text-stroke: 4px black; }
h1.stroke-fs { font-size: 2.7em; }

.stack { position: relative; }
.stack > h1:not(:first-child) {
  left: 0;
  margin: 0;
  position: absolute;
  top: 0;
}


.stroke-after:after { 
  -webkit-text-stroke: 4px black;
  content: attr(value);
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
Stack
<div class="stack">
  <h1 class="stroke">WHAT CARRER SHOULD YOU HAVE ?</h1>
  <h1>WHAT CARRER SHOULD YOU HAVE ?</h1>
</div>

<hr />

After
<div style="position: relative">
  <h1 class="stroke-after" value="WHAT CARRER SHOULD YOU HAVE ?">WHAT CARRER SHOULD YOU HAVE ?</h1>
</div>

<hr />

Native
<h1 class="stroke">WHAT CARRER SHOULD YOU HAVE ?</h1>

<hr />

Font size
<h1 class="stroke stroke-fs">WHAT CARRER SHOULD YOU HAVE ?</h1>
BrunoLM
  • 97,872
  • 84
  • 296
  • 452
1

I have also tried text-shadow and -webkit-text-stroke, but unsuccessful in all ways. I could only get the result by making two divs(background and foreground) i.e,

background div with -webkit-text-stroke and foreground div without -webkit-text-stroke.

<div style="position:absolute;width:1280px;height:720px;left:0px;top:0px" >
    <div style="color:#CDCDCD;
                font-family:sans-serif;
                font-size:57px;
                -webkit-text-stroke-color:black;
                -webkit-text-stroke-width:0.04em;" >
        <p>
            Text with outine stroke outwards.
        </p>
    </div>
</div>

<div style="position:absolute;width:1280px;height:720px;left:0px;top:0px" >
    <div style="color:#CDCDCD;
                font-family:sans-serif;
                font-size:57px;" >
        <p>
            Text with outine stroke outwards.
        </p>
    </div>
</div>

Bt the only problem with the html generated through is twice the size(size in memory) as the div is repeated twice.

Does anyone has some better solution?

Prateek
  • 51
  • 6
1

I know this is an old question but have a look at this:

https://jsfiddle.net/1dbta9cq/

you can simply place another text on top of the stroked text with absolute positioning, it's not so tidy but it solves the problem

<div class="container">
  <h1 class="stroke">Stroke</h1>
  <h1 class="no-stroke">Stroke</h1>
</div>

.container{
  width:300px;
}

.stroke{
  position:absolute;
  -webkit-text-stroke: 10px black;
}

.no-stroke{
  color:white;
  position:absolute
}
Martin
  • 22,212
  • 11
  • 70
  • 132
Dan Levin
  • 718
  • 7
  • 16
1

Here’s a SCSS mixin for simulating text outlines with multiple shadows:

@use "sass:math";
@use 'sass:list';

@mixin text-outline($offset, $color, $num-steps: 16) {
  $shadows: ();
  @for $i from 0 to $num-steps {
    $angle: $i * 360deg / $num-steps;
    $x: calc(#{math.cos($angle)} * #{$offset});
    $y: calc(#{math.sin($angle)} * #{$offset});
    $shadows: list.append($shadows, #{$x} #{$y} 0 #{$color}, $separator: comma);
  }
  text-shadow: $shadows;
}

For larger offsets, a greater number of shadows (“steps”) is required for good results. Note that in general, however, more shadows will be more expensive for the browser to render.

Example:

span {
  color: #FF004D;
  @include utils.type-outline(
    $offset: calc(0.05rem + 0.05em),
    $color: #FFFFFF,
    $num-steps: 32
  );
}

Result: white outline around text

Luke Taylor
  • 8,631
  • 8
  • 54
  • 92
0

Here's a SASS mixin I created to make it easy to create a text outline using the prefixed properties (-webkit, -moz) when supported, but falling back to just a color with text shadow. Note that the fallback doesn't work well with opaque fill colors, but other than that I think it's a pretty good solution, until we can get a standard method that has better browser support.

Fiddle

Mixin

@mixin text-stroke($fill-color, $stroke-color, $stroke-width) {
  color: $fill-color;
  text-shadow: -$stroke-width -$stroke-width 0 $stroke-color,
                $stroke-width -$stroke-width 0 $stroke-color,
               -$stroke-width  $stroke-width 0 $stroke-color,
                $stroke-width  $stroke-width 0 $stroke-color;

  @supports ((-webkit-text-stroke-color: $stroke-color) and (-webkit-text-fill-color: white))
            or ((-moz-text-stroke-color: $stroke-color) and (-moz-text-fill-color: white)) {
                        color: unset;
                  text-shadow: unset;
         -moz-text-fill-color: $fill-color;
      -webkit-text-fill-color: $fill-color;
       -moz-text-stroke-color: $stroke-color;
    -webkit-text-stroke-color: $stroke-color;
       -moz-text-stroke-width: $stroke-width;
    -webkit-text-stroke-width: $stroke-width;
  }
}

Example Usage

(Makes text semi-transparent black with a white outline)

.heading-outline {
  @include text-stroke(rgba(#000,.5), #fff, 1px);
}
dmbaughman
  • 578
  • 2
  • 7
0

I'm obviously late to the party but I find the easiest is probably the best way forward to a solution.

For me, I did the outside stroke effect by just making the weight of the font larger.

body { background-color: gray; }
h1 {
  color: white;
  font-size: 2.5em;
  font-weight: bold;
  -webkit-text-stroke: 2px black;
}

Is it the most correct ?
Depending on the situation then the answer is yes Or no.

Does it work in your use case ?
...for me , yes perfefctly.

0

Add a custom attribute which is the same as the node's text content, and define a pseudo-class ::before which acts as the non-stroked text, which will be rendered in front of the stroked text, creating the desired illusion of a text being stroked on the outside only:

label {
  user-select: none;
}

label:has(:checked) ~ .stroke::before {
  content: attr(data-text);
  position: absolute;
  pointer-events: none; /* for text selection */
  -webkit-text-stroke: 0px;
}

.stroke {
  margin: .5em;
  font: 60px arial;
  
  /* trick */
  position: relative;
  -webkit-text-stroke: 20px purple;
  color: cyan;
}
<label>
  <input type=checkbox checked> With trick
</label>
<h1 class="stroke" data-text="Nice little trick">Nice little trick</h1>

In the future, when Chrome solves this, you could simply delete the ::before selector.


I've opened a request in the CSS community to improve the content property: https://github.com/w3c/csswg-drafts/issues/8894

vsync
  • 118,978
  • 58
  • 307
  • 400
0

Html

<div className="text-stoke" data-text="Your text">Your text</div>

Css

.text-stoke {
    position: relative;
    color: white;

    &:after {
        content: attr(data-text);
        position: absolute;
        left: 0;
        top: 0;
        -webkit-text-stroke: 2px var #000;
        -webkit-text-fill-color: transparent;
        z-index: -1;
    }
}
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 16 '23 at 09:05
-1

Text shadow might be used to achieve this result http://css3generator.com

  • 1
    With `text-shadow` you can just get some blurred shadow and the best you can get for border solid with this is just 1px ... I don't think the OP wants that. Also avoid just link answer include some code an example to get upvotes :) – DaniP Oct 29 '14 at 15:33
  • Not really a fair comment, especially considering the correct answer and its improvement. However, the answer still doesn't support an upvote. The broken link, for one thing. – Parapluie Sep 28 '21 at 18:22