Lesson 9 of 25

CSS Borders

A Border Needs a Style Before It Exists

A border is described by three things: how thick it is, what kind of line it draws, and what colour it is. The order in the shorthand does not matter, so border: 2px solid #d1039e and border: solid #d1039e 2px are both valid, but the shorthand form is what you will see everywhere.

The part that catches people is the default. border-style starts at none, which means no line at all — so setting only a width and a colour gives you nothing. Write border-width: 5px; border-color: red; and absolutely nothing appears on screen, with no error to explain why. The style is the switch that turns a border on, and without it the other two properties have nothing to describe.

The colour has a useful default of its own. If you leave border-color unset, the border takes the element's current text colour. That means a link with a bottom border automatically gets a matching border when it changes colour on hover — you only have to change color and the line follows.

Example
/* Nothing appears — style defaults to none */
.broken {
  border-width: 5px;
  border-color: red;
}

/* Works */
.fixed { border: 5px solid red; }

/* Leave the colour out and it follows the text colour */
.tag {
  color: #d1039e;
  border: 1px solid;    /* pink, because the text is pink */
}
.tag:hover {
  color: #7a0060;       /* the border changes with it */
}

/* The line styles worth knowing */
.s1 { border: 2px solid  #333; }   /* a plain line */
.s2 { border: 2px dashed #333; }   /* dashes — good for drop zones */
.s3 { border: 2px dotted #333; }
.s4 { border: 4px double #333; }   /* two thin lines */
.s5 { border: 2px none   #333; }   /* explicitly nothing */
Notes
  • To remove a border that came from somewhere else — a browser default on a button or input, or an earlier rule — write border: none or border: 0. Both work. Setting border-color: transparent does not remove it; the line is still there taking up space, just invisible, which is occasionally exactly what you want.

One Side at a Time, and Why That Is So Useful

Every border property has a per-side version: border-top, border-right, border-bottom, border-left, plus the individual border-bottom-width-style longhands when you need to change one aspect of one side. In practice, single-side borders do far more design work than four-sided boxes.

Three patterns cover most real interfaces. A thick border-left turns a plain block into a quotation or a callout — the coloured bar down the side is instantly recognisable and costs one declaration. A border-bottom on list items makes a clean divided list; pair it with the sibling selector from the selectors lesson so the last item does not get a stray line under it. And a border-bottom on the active item in a navigation bar is the standard way to show which page you are on.

Tables have one extra rule of their own. Each cell draws its own border, so adjacent cells produce a double-thick line between them. border-collapse: collapse on the table merges neighbouring borders into one, which is nearly always what you want.

Example
/* Callout with a coloured bar */
.notice {
  border-left: 4px solid #d1039e;
  background: #fdf2fa;
  padding: 16px 20px;
}

/* Divided list, with no line after the last item */
.list-item {
  padding: 12px 0;
  border-bottom: 1px solid #e3e3ea;
}
.list-item:last-child {
  border-bottom: none;
}

/* Active tab in a nav bar */
.nav a          { padding: 12px 4px; border-bottom: 3px solid transparent; }
.nav a.is-active { border-bottom-color: #d1039e; }

/* Tables: merge the doubled lines */
table { border-collapse: collapse; }
th, td { border: 1px solid #e3e3ea; padding: 8px 12px; }
  • border-top, border-right, border-bottom, border-left — one side each
  • A thick left border is the cheapest way to build a callout or blockquote
  • border-collapse: collapse stops table cells drawing doubled borders
  • A transparent border in the base state reserves the space so the active state does not shift anything
  • Borders count towards an element's size unless box-sizing: border-box is set

Rounded Corners and Their One Surprise

border-radius rounds the corners of a box, and it works whether or not the element actually has a border — it clips the background and the border together. One value rounds all four corners. Four values run clockwise from the top-left: top-left, top-right, bottom-right, bottom-left. You can also address a single corner by name with properties such as border-top-left-radius.

Percentages are relative to the element's own size, which gives you the familiar trick: border-radius: 50% on a square element makes a perfect circle, and on a rectangle makes an ellipse. For pill-shaped buttons, a large fixed value such as 999px is more reliable than 50%, because the corners round as far as they can and the straight middle section stays straight however wide the button gets.

The surprise arrives when the element has children with their own backgrounds. Rounding a card's corners does not round its contents — an image or a coloured header inside it will happily stick out past the curve, leaving square corners poking through a rounded card. The fix is overflow: hidden on the rounded parent, which clips everything inside to the same shape. This is one of those bugs that looks mysterious in a screenshot and takes one line to solve once you have seen it.

Example
/* All four corners, then per-corner */
.card   { border-radius: 12px; }
.tab    { border-radius: 8px 8px 0 0; }   /* rounded top only */
.speech { border-radius: 12px 12px 12px 0; }

/* Circles and pills */
.avatar { width: 64px; height: 64px; border-radius: 50%; }
.pill   { padding: 8px 20px; border-radius: 999px; }

/* The classic bug: the image escapes the rounded corners */
.card {
  border-radius: 12px;
  overflow: hidden;      /* <- the fix */
}
.card img {
  width: 100%;
  display: block;
}

/* Elliptical corners: horizontal radii / vertical radii */
.leaf { border-radius: 40px / 20px; }
Notes
  • overflow: hidden clips children in every direction, so do not use it on a card that also contains a dropdown menu or a tooltip meant to escape the box — those will be cut off. In that case round the child's own corners instead.

Border, Outline or Shadow? Choosing the Right Line

Three different properties can draw a line around an element, and they behave differently in ways that matter.

A border is part of the box. It occupies real space, so adding one changes the element's size unless you are using border-box, and even then it eats into the content area. This produces a very common annoyance: a card with no border that gains border: 2px solid on hover visibly jumps by two pixels as the mouse arrives. The clean fix is to give it a transparent border in the resting state, so the space is already reserved and only the colour changes.

An outline is drawn outside the border and takes up no space whatsoever, so it can never shift a layout. That is precisely why browsers use it for focus rings. outline-offset pushes it further out for breathing room. Never delete a focus outline without providing a clearly visible replacement — someone navigating your page by keyboard has no other way to see where they are.

A box-shadow with no blur and a spread value looks exactly like a border and, like an outline, occupies no layout space. It follows border-radius automatically and you can stack several, which makes it the usual choice for a two-colour focus ring or for a border you want to animate without anything moving.

Example
/* Hover that shifts the layout — avoid */
.card       { border: none; }
.card:hover { border: 2px solid #d1039e; }   /* everything jumps 2px */

/* Reserve the space up front */
.card       { border: 2px solid transparent; }
.card:hover { border-color: #d1039e; }       /* nothing moves */

/* Focus ring: takes no space, so it is always safe */
.button:focus-visible {
  outline: 3px solid #d1039e;
  outline-offset: 2px;
}

/* A "border" made of shadow — no blur, only spread */
.chip {
  box-shadow: 0 0 0 2px #d1039e;
}

/* Two rings at once, which a border cannot do */
.input:focus {
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #d1039e;
}
  • Border — part of the box, takes space, follows border-radius
  • Outline — outside the box, takes no space, ideal for focus states
  • Box-shadow with spread — no space, follows border-radius, can be layered
  • Reserve border space with a transparent border to stop hover states shifting the page
  • If a design ever calls for removing focus styling, replace it with something equally visible
Ask AI