You set border-radius: 50% and expect a perfect circle, yet the result is an oval. The reason is not a bug, it is geometry. By the end of this article you will know exactly how border-radius: 50% behaves, how to guarantee circles, how to intentionally create ovals, and how to apply this to images, avatars, and responsive components.
Why border-radius: 50% Matters
The 50% value is the simplest way to round a box into a circle or an ellipse with a single declaration. It is widely supported, lightweight, and plays well with layout primitives like flexbox and grid. Understanding when it produces a circle versus an oval helps you build precise UI: circular avatars, elliptical badges, pills, and decorative shapes without extra wrappers or SVGs. Once you grasp the math, you can control the shape confidently and avoid layout surprises.
Prerequisites
You only need basic CSS and a few modern layout habits. A quick refresher on custom properties and pseudo-elements helps when you expand this pattern into real components.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
We will build a small shape gallery that demonstrates circles, ovals, and circular images using the same border-radius: 50% rule. The markup includes a wrapper, a heading per demo, and a set of boxes. Each shape is a single element, with one case wrapping an image to show the avatar pattern.
<!-- HTML -->
<section class="demo">
<h3>Circle vs. Oval: border-radius: 50%</h3>
<div class="row">
<div class="shape shape--circle-50" aria-hidden="true"></div>
<div class="shape shape--oval-50" aria-hidden="true"></div>
<div class="shape shape--ellipse-ratio" aria-hidden="true"></div>
</div>
<h3>Circular and Elliptical Images</h3>
<div class="row">
<figure class="avatar avatar--circle">
<img src="https://picsum.photos/id/1027/600/600" alt="Portrait avatar">
<figcaption>Circle (avatar)</figcaption>
</figure>
<figure class="avatar avatar--oval">
<img src="https://picsum.photos/id/1062/800/600" alt="Landscape photo">
<figcaption>Oval (thumbnail)</figcaption>
</figure>
</div>
<h3>Interactive: Hover to Morph</h3>
<div class="row">
<div class="shape shape--morph" role="img" aria-label="Shape morphing between circle and oval"></div>
</div>
</section>
Step 2: The Basic CSS & Styling
Set up a neutral stage with CSS variables to control sizes and colors. The grid makes it easy to line up shapes. The .shape class handles common sizing, while modifiers define whether a box is square or rectangular, and whether it should be treated as a circle or an oval.
/* CSS */
:root {
--size: 10rem;
--oval-w: 16rem;
--oval-h: 10rem;
--gap: 1.25rem;
--bg: #0f172a; /* slate-900 */
--card: #111827; /* gray-900 */
--ink: #e5e7eb; /* gray-200 */
--accent: #60a5fa; /* blue-400 */
--accent-2: #f472b6;/* pink-400 */
}
*,
*::before,
*::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
color: var(--ink);
background: radial-gradient(1200px 800px at 10% -10%, #1f2937, var(--bg));
line-height: 1.55;
padding: 2rem;
}
.demo {
max-width: 1000px;
margin: 0 auto;
background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01));
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
padding: 1.5rem;
}
.demo h3 {
margin: 1rem 0 0.75rem;
font-weight: 600;
}
.row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--gap);
margin-bottom: 1.25rem;
}
.shape {
display: grid;
place-items: center;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
box-shadow:
inset 0 0 0 2px rgba(255,255,255,0.2),
0 10px 20px rgba(0,0,0,0.35);
color: #0b1020;
font-weight: 700;
user-select: none;
}
.avatar {
display: grid;
gap: 0.5rem;
justify-items: center;
color: var(--ink);
text-align: center;
}
.avatar img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar figcaption {
font-size: 0.9rem;
opacity: 0.85;
}
Advanced Tip: Centralizing dimensions in CSS variables keeps demos consistent. You can theme entire shape sets by changing –accent colors once, or scale all shapes by updating –size and –oval-* in a single place.
Step 3: Building the Shape Set
Now we define three variants that show how border-radius: 50% behaves with different aspect ratios. One is a circle (square box), one is an oval (rectangular box), and one uses aspect-ratio to demonstrate an ellipse independent of explicit height.
/* CSS */
.shape--circle-50 {
width: var(--size);
aspect-ratio: 1 / 1; /* square box */
border-radius: 50%;
}
.shape--oval-50 {
width: var(--oval-w);
height: var(--oval-h); /* rectangular box */
border-radius: 50%;
}
.shape--ellipse-ratio {
width: calc(var(--size) * 1.5);
aspect-ratio: 3 / 2; /* rectangular box via ratio */
border-radius: 50%;
background: linear-gradient(135deg, #34d399, #22d3ee);
}
How This Works (Code Breakdown)
border-radius accepts either length or percentage radii. When you pass a single percentage like 50%, the browser computes horizontal and vertical corner radii relative to the element’s own box. That means each corner uses rx = 50% of width and ry = 50% of height. If width equals height (a square), rx equals ry, and the outline becomes a perfect circle. That is exactly what .shape–circle-50 does by pairing aspect-ratio: 1 / 1 with border-radius: 50%.
When width and height differ, rx and ry differ. The curve remains smooth, but now it traces an ellipse inscribed in the rectangle. That is what you see in .shape–oval-50 and .shape–ellipse-ratio. Any rectangle with border-radius: 50% becomes an ellipse whose radii are half the element’s width and height. If you want a refresher on exact recipes, see how to make a circle with CSS and how to make an oval with CSS. For ratio-driven layouts, the ellipse method also maps to the patterns in how to make an ellipse with CSS.
A small implementation detail helps you reason about edge cases. CSS enforces that corner radii cannot exceed half the box size; if they would, the browser scales them down proportionally. Using 50% guarantees you always hit the “full rounding” limit, which is why you get a circle for a square and a true ellipse for a rectangle.
Step 4: Building the Image Avatars and Morph Demo
Images often fool developers because their intrinsic aspect ratio can be different from the intended display box. The trick is to constrain the box to a square for circles or a rectangle for ovals, then clip the image with overflow: hidden and border-radius: 50%. For an interactive demo, we will also morph a shape on hover by changing its width using transforms, which keeps animation smooth.
/* CSS */
.avatar--circle {
width: var(--size);
aspect-ratio: 1 / 1; /* square avatar box */
border-radius: 50%;
overflow: hidden;
}
.avatar--oval {
width: var(--oval-w);
height: var(--oval-h); /* rectangular avatar box */
border-radius: 50%;
overflow: hidden;
}
/* Morph demo: start as a circle, stretch to an oval on hover */
.shape--morph {
width: var(--size);
aspect-ratio: 1 / 1;
border-radius: 50%;
background: conic-gradient(from 120deg, #a78bfa, #60a5fa, #34d399, #f472b6, #a78bfa);
transition: transform 350ms cubic-bezier(.2,.7,0,1);
will-change: transform;
}
/* ScaleX morph keeps paint cheap compared to animating width/height */
.shape--morph:hover {
transform: scaleX(1.6); /* turns circle into an ellipse visually */
}
How This Works (Code Breakdown)
The avatar selectors set a predictable box. For a circle, the box is square by design using aspect-ratio: 1 / 1. The image fills the box with object-fit: cover, and the parent’s border-radius: 50% clips it to a circle. For an oval, you use different width and height, then the same 50% rounding produces an ellipse. The overflow: hidden line is the clip gate; without it, a child image could bleed outside the rounded corner if it uses transforms or on some GPU compositing paths.
The morph demo shows a performant way to animate between circular and elliptical appearances. Instead of changing width and height, which triggers layout and repaint, transform: scaleX scales the shape on the compositor. Because border-radius affects the clipping path, scaling the whole element yields an ellipse without heavy reflows. The element starts as a circle, and on hover it stretches horizontally to an oval while preserving the 50% rounding.
Advanced Techniques: Precision Control and Responsive Patterns
Now that the 50% rule is clear, you can push it further for fluid components. Use clamp() to scale sizes with the viewport, use container queries for slot-aware avatars, and combine multiple radii for capsules versus ellipses.
/* CSS */
:root {
--avatar-size: clamp(64px, 12vw, 160px);
--ellipse-w: clamp(180px, 30vw, 380px);
--ellipse-ratio: 5 / 3;
}
/* Fluid circle using border-radius: 50% */
.fluid-circle {
width: var(--avatar-size);
aspect-ratio: 1 / 1;
border-radius: 50%;
background: linear-gradient(135deg, #fb7185, #f59e0b);
}
/* Fluid ellipse controlled by ratio */
.fluid-ellipse {
width: var(--ellipse-w);
aspect-ratio: var(--ellipse-ratio);
border-radius: 50%;
background: linear-gradient(135deg, #38bdf8, #22c55e);
}
/* A stadium (pill) is different: huge length radius, not 50% ellipse */
.pill {
min-width: 12rem;
padding: 0.5rem 1.25rem;
border-radius: 9999px; /* very large value, not ellipse */
background: #111827;
border: 1px solid rgba(255,255,255,.1);
color: var(--ink);
display: inline-flex;
justify-content: center;
}
The circle uses a fluid width and a 1:1 ratio, so border-radius: 50% resolves to equal rx and ry at all sizes. The ellipse uses a fluid width and a fixed aspect-ratio, so rx and ry change proportionally with the viewport. The pill example clarifies a common confusion: a stadium shape is not the same as a 50% ellipse. A pill keeps straight segments with semicircular ends because the box remains rectangular with a very large corner radius; an ellipse has no straight edges at all.
Accessibility & Performance
Shapes built for decoration should not distract screen reader users, and animations should respect user preferences. You also want smooth interactions without layout thrash.
Accessibility
Mark purely decorative shapes with aria-hidden=”true” or role=”presentation”. For meaningful shapes or avatars, include alt text that describes purpose, not just “image.” When a shape encodes state (like online presence), ensure that state is conveyed with text or aria-live. Respect motion preferences with a guard around non-essential animations.
/* CSS */
@media (prefers-reduced-motion: reduce) {
.shape--morph {
transition: none;
transform: none;
}
}
Performance
border-radius is cheap to paint and clip. The costly part is layout. Avoid animating width and height when morphing between circle and oval; use transforms such as scaleX or scaleY for compositor-friendly animations. Heavy shadows and large filters can add GPU cost, so keep them subtle or static. Pre-size images to expected box sizes, or rely on aspect-ratio with a known width to prevent layout shifts when images load.
Mastering the Circle-or-Oval Switch
border-radius: 50% is predictable: a square box yields a circle, a rectangular box yields an ellipse. You now have patterns for layout-driven shapes, images, and responsive designs, plus a performant morph technique. Use these rules to craft precise avatars, badges, and shape libraries with confidence.