CSS border-left and the other single sides

border-left: 4px solid #4f46e5 draws a line on the left edge only. Each side has its own property, and each of those splits into width, style and colour.

To put a border on one side, use the property for that side: border-left, border-top, border-right or border-bottom. Each takes the same three values as border, so border-left: 4px solid #4f46e5 draws a 4px line down the left edge and nothing else.

Tick the sides below and watch the CSS it prints. Only ticked sides get a line.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>One border side at a time</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; font-size: 14px; }
  .controls label { display: inline-flex; align-items: center; gap: 5px; }
  .box {
    margin: 18px 0 14px; padding: 18px; max-width: 320px;
    background: #fff; font-size: 15px;
  }
  pre { margin: 0; padding: 10px 12px; min-height: 40px; background: #1d2330; color: #e5e7eb;
        border-radius: 8px; font: 13px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; }
</style>
</head>
<body>
<div class="controls">
  <label><input type="checkbox" id="top"> top</label>
  <label><input type="checkbox" id="right"> right</label>
  <label><input type="checkbox" id="bottom"> bottom</label>
  <label><input type="checkbox" id="left" checked> left</label>
</div>
<div class="controls" style="margin-top:10px">
  <label>width <input type="range" id="width" min="1" max="16" value="6"></label>
  <label>style
    <select id="style"><option>solid</option><option>dashed</option><option>dotted</option><option>double</option></select>
  </label>
  <label>colour <input type="color" id="color" value="#4f46e5"></label>
</div>

<div class="box" id="box">Tick a side. Each ticked side gets its own <b>border-*</b> line.</div>
<pre id="out"></pre>

<script>
  const box = document.getElementById('box');
  const out = document.getElementById('out');
  const sides = ['top', 'right', 'bottom', 'left'];

  function update() {
    const w = document.getElementById('width').value + 'px';
    const s = document.getElementById('style').value;
    const c = document.getElementById('color').value;
    let css = '';
    box.style.border = '0';  // start from no border, then add the ticked sides
    for (const side of sides) {
      if (!document.getElementById(side).checked) continue;
      const value = w + ' ' + s + ' ' + c;
      box.style.setProperty('border-' + side, value);
      css += 'border-' + side + ': ' + value + ';\n';
    }
    out.textContent = css || 'border: 0;';
  }

  document.querySelectorAll('input, select').forEach((el) => el.addEventListener('input', update));
  update();
</script>
</body>
</html>
Tick one or more sides, then change the width, style and colour. The box on the page and the CSS under it change together.

The general border property, colours and border-radius are covered in CSS border. This page stays with single sides.

Four sides, each with three longhands

Every side is a shorthand of its own. border-left sets three longhands at once: border-left-width, border-left-style and border-left-color. The same pattern holds for top, right and bottom, which makes twelve longhands in all.

Left: the four side properties, joined on diagonals at the corners. Right: the three longhands behind border-left.
Left: the four side properties, joined on diagonals at the corners. Right: the three longhands behind border-left.
Property Sets Example
border-left Width, style and colour of one side border-left: 4px solid red
border-left-width Thickness only border-left-width: 6px
border-left-style Line type only border-left-style: dashed
border-left-color Colour only border-left-color: teal
border-width All four widths, top then clockwise border-width: 0 0 2px 4px

Use a longhand when you want to change one part and keep the rest. A hover state that only swaps the colour is a good fit:

.item { border-left: 4px solid transparent; }
.item:hover { border-left-color: #4f46e5; }

If the colour is left out, the side uses currentcolor, the text colour of the element.

The shorthand resets what you leave out

border-left: 4px looks reasonable and draws nothing. A shorthand sets every longhand it covers, and anything missing goes back to its starting value. The starting style is none, and a side whose style is none has a computed width of 0.

The same rule decides the order of lines. The four-side border also resets border-left, so write it first and override the side after it:

.note {
  border: 1px solid #e1e4ea;       /* all four sides */
  border-left: 5px solid #4f46e5;  /* then one side on top */
}

Swap those two lines and the thick left bar is gone, because border comes last and wins.

To remove a single side, use border-left: none or border-left: 0. Both leave the side with no visible line.

A left accent bar for notes and quotes

The most common one-sided border is a coloured bar on the left of a callout, a quote or the current item in a menu. It takes four steps:

  1. Give the box border-left with a width, style and colour.
  2. Add padding-left so text does not touch the bar.
  3. Round only the right corners.
  4. Switch to border-inline-start if the page can be shown right-to-left.
.callout {
  padding: 10px 14px;
  background: #fff;
  border-left: 5px solid #4f46e5;
  border-radius: 0 8px 8px 0;  /* right corners only */
}

Step 3 matters. With border-radius on all four corners, the bar follows the curve at the top and bottom and thins out towards its ends.

That happens because the other sides have no width to share the corner with. Rounding only the far corners keeps the bar straight.

The bar also adds to the box size. A 5px bar makes the box 5px wider unless the box uses box-sizing: border-box, explained in box-sizing.

border-block and border-inline: logical sides

The physical sides stay put whatever the language. The logical sides follow the direction of the text, which matters for Arabic, Hebrew and other right-to-left scripts.

Logical property In English (left to right) In right-to-left text
border-inline-start Left Right
border-inline-end Right Left
border-block-start Top Top
border-block-end Bottom Bottom
border-inline Left and right Right and left
border-block Top and bottom Top and bottom

border-block and border-inline set two sides at once, so border-block: 1px solid #ddd gives a box a top and a bottom rule. Each logical side also has width, style and colour longhands, such as border-inline-start-color.

In vertical writing modes the block and inline axes turn, so border-block-start is no longer the top. CSS writing-mode shows how. The full set of logical properties, beyond borders, is in CSS logical properties.

Underlines: border-bottom vs text-decoration

A line under text can be an underline or a bottom border. They look alike on one short word and behave differently everywhere else.

text-decoration draws on the letters. border-bottom draws under the box.
text-decoration draws on the letters. border-bottom draws under the box.
text-decoration border-bottom
Where the line sits Near the baseline, adjustable Under the box, below the descenders
Letters with tails (g, y, p) Can leave a gap around them Line runs straight through underneath
On a heading or other block As long as the text The full width of the block
Takes up space No On a block, yes. On inline text, no
Style options Solid, double, dotted, dashed, wavy Solid, double, dotted, dashed, groove and others

Drag the slider to narrow the columns and see how each one wraps.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>border-bottom vs text-decoration</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .row { display: flex; flex-wrap: wrap; gap: 12px; }
  .col { flex: 1 1 150px; min-width: 0; background: #fff; border-radius: 10px; padding: 12px 14px; }
  .col code { font: 12px ui-monospace, Consolas, monospace; color: #4f46e5; }
  p { font-size: 17px; line-height: 1.6; margin: 10px 0 0; }
  h3 { font-size: 17px; margin: 14px 0 0; }

  /* Left: a real underline */
  .deco { text-decoration: underline 2px; text-underline-offset: 3px; }
  /* Right: a border drawn under the box */
  .line { border-bottom: 2px solid; }
  /* Headings are blocks, so their border runs the full width... */
  h3.fit { width: fit-content; }  /* ...unless the box shrinks to the text */

  label { display: block; font-size: 14px; margin-bottom: 12px; }
</style>
</head>
<body>
<label>Column width <input type="range" id="w" min="130" max="320" value="190"></label>
<div class="row">
  <div class="col">
    <code>text-decoration</code>
    <p>A <span class="deco">quickly typed, gappy underline</span> on wrapped text.</p>
    <h3 class="deco">Heading</h3>
  </div>
  <div class="col">
    <code>border-bottom</code>
    <p>A <span class="line">quickly typed, gappy underline</span> on wrapped text.</p>
    <h3 class="line">Heading</h3>
    <h3 class="line fit">Heading, fit-content</h3>
  </div>
</div>

<script>
  // Narrow the columns to see how each underline wraps
  const w = document.getElementById('w');
  w.addEventListener('input', () => {
    document.querySelectorAll('.col').forEach((c) => {
      c.style.flex = '0 1 ' + w.value + 'px';
    });
  });
  w.dispatchEvent(new Event('input'));
</script>
</body>
</html>
The same sentence and heading with text-decoration on the left and border-bottom on the right. The last heading uses width: fit-content.

Two things to notice. On a wrapped inline element, each line gets its own bottom border.

On a heading the border runs to the edge of the column, because the heading is a block. width: fit-content or display: inline-block shrinks the box to the text.

A thick bottom border on inline text does not push the lines apart. Vertical borders and padding on inline elements do not change the line height, so a heavy border can overlap the next line. Increase line-height if that happens.

For readable underlines on links, CSS text-decoration covers text-underline-offset and thickness.

Triangles made from borders

Where two borders meet, the browser splits the corner on a diagonal. Shrink the box to nothing and the four borders become four triangles.

A box with four thick borders, the same box at width 0 and height 0, and one side left visible.
A box with four thick borders, the same box at width 0 and height 0, and one side left visible.
.arrow-down {
  width: 0;
  height: 0;
  border: 8px solid transparent;
  border-top-color: #1d2330;
}

The coloured side decides the direction: border-top-color gives a triangle pointing down, border-left-color one pointing right. For a flatter or taller shape, set the widths separately. border-width: 10px 8px 0 makes a down arrow 16px wide and 10px tall.

The usual place for this is the little arrow on a tooltip or a speech bubble, drawn with ::after so no extra element is needed. A pure HTML tooltip uses the same trick.

A finished example: callouts, tabs and a bubble

This example uses only side borders. The callouts use border-inline-start, the tabs use border-bottom, and the bubble arrow is a border triangle. Press the button to flip the page to right-to-left.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Callouts, tabs and a bubble built from border sides</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  button { font: inherit; cursor: pointer; }
  .flip { padding: 6px 12px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; font-size: 14px; }

  /* Tabs: every tab reserves a transparent bottom border, so nothing jumps */
  .tabs { display: flex; gap: 4px; margin: 14px 0 12px; border-bottom: 1px solid #d5d9e0; }
  .tab { padding: 8px 12px; background: none; border: 0; border-bottom: 3px solid transparent;
         margin-bottom: -1px; color: #5b6472; font-size: 15px; }
  .tab.on { border-bottom-color: #4f46e5; color: #1d2330; font-weight: 600; }

  /* Callout: one logical side, so it moves to the right in right-to-left text */
  .callout { padding: 10px 14px; margin: 0 0 10px; background: #fff; font-size: 15px;
             border-inline-start: 5px solid var(--c); border-start-end-radius: 8px; border-end-end-radius: 8px; }
  .info { --c: #4f46e5; } .warn { --c: #d97706; } .ok { --c: #16a34a; }

  /* Bubble with an arrow made of borders */
  .bubble { position: relative; display: inline-block; margin-top: 8px; padding: 8px 12px;
            background: #1d2330; color: #fff; border-radius: 8px; font-size: 14px; }
  .bubble::after {
    content: ""; position: absolute; top: 100%; inset-inline-start: 18px;
    width: 0; height: 0;
    border: 8px solid transparent;  /* four transparent triangles */
    border-top-color: #1d2330;      /* show only the top one: it points down */
  }
</style>
</head>
<body>
<div id="app" dir="ltr">
  <button class="flip" id="flip">Switch to right-to-left</button>

  <div class="tabs" role="tablist">
    <button class="tab on" role="tab">Notes</button>
    <button class="tab" role="tab">Warnings</button>
    <button class="tab" role="tab">Done</button>
  </div>

  <p class="callout info" data-tab="0">Info: border-inline-start draws this bar</p>
  <p class="callout warn" data-tab="1" hidden>Warning: the bar is 5px wide</p>
  <p class="callout ok" data-tab="2" hidden>Done: no extra element needed</p>

  <div class="bubble">Saved: the arrow is a border</div>
</div>

<script>
  const app = document.getElementById('app');
  const tabs = document.querySelectorAll('.tab');

  // Flip the writing direction: the logical bar and the arrow follow
  document.getElementById('flip').addEventListener('click', (e) => {
    const rtl = app.dir === 'ltr';
    app.dir = rtl ? 'rtl' : 'ltr';
    e.target.textContent = rtl ? 'Switch to left-to-right' : 'Switch to right-to-left';
  });

  // Tabs: move the coloured bottom border and show the matching callout
  tabs.forEach((tab, i) => tab.addEventListener('click', () => {
    tabs.forEach((t) => t.classList.toggle('on', t === tab));
    document.querySelectorAll('.callout').forEach((c) => { c.hidden = c.dataset.tab != i; });
  }));
</script>
</body>
</html>
The bar and the arrow move to the right in right-to-left mode. The active tab marker is a bottom border.
  • Callouts: border-inline-start puts the bar where text starts.
  • Tabs: every tab has a 3px transparent bottom border. Only its colour changes, so nothing jumps.
  • Bubble: the arrow sits at top: 100% and uses inset-inline-start, so it moves with the text direction too.

When it does not work

What you see Cause Fix
border-left: 4px draws nothing No style, so the side is none Add a style, such as solid
The side border vanished A later border line reset it Put the side property after border
The bar curves and thins at its ends border-radius on all corners Round only the far corners
A heading underline runs across the page The heading is a block width: fit-content
Tabs jump when one becomes active Only the active tab has a border Give all tabs a transparent one
A thick underline overlaps the next line Inline borders do not affect line height Increase line-height
The bar stays on the left in Arabic border-left is physical Use border-inline-start
The triangle is a square or a trapezoid The box has width, height or padding width: 0, height: 0, no padding

Border tricks are easiest to judge in the browser, where you can flip the direction and resize the column. A screenshot shows one state, and an .html attachment may open as plain code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it. The page renders as written and its scripts run, so the people you send it to can press the buttons themselves.

If you change the code later, the same link shows the new version.

Questions people ask

How do I add a border to only the left side in CSS?

Use border-left with a width, a style and a colour, for example border-left: 4px solid #4f46e5. The other three sides keep their own values, which start as no border.

Why does border-left: 4px not show anything?

The shorthand resets every part you leave out. Without a style, border-left-style goes back to none, and a side with style none has a width of 0. Add a style such as solid.

What is the difference between border-left and border-inline-start?

border-left is always the physical left edge. border-inline-start is the edge where a line of text starts: the left in English, the right in Arabic or Hebrew. Use the logical one when a page may be shown right-to-left.

Should I underline text with border-bottom or text-decoration?

Use text-decoration for text: it follows the letters, can leave gaps for descenders and stays inside the line. Use border-bottom when you want a line under the whole box, such as a divider under a heading or the marker under an active tab.

How does a CSS triangle made with borders work?

Where two borders meet, the corner is split on a diagonal. With width and height at 0, the four borders become four triangles. Make three of them transparent and one triangle is left.

Keep reading