A close button is a small control that carries a lot of responsibility. It must be obvious, fast to click or tap, crisp at any size, easy to theme, and accessible to keyboard and screen reader users. In this guide you will build a production-ready Close button using pure CSS: a scalable “X” icon, optional circular or square backgrounds, focus and hover states, motion preferences, and robust semantics.
Why Close Button Design Matters
The close button appears in modals, drawers, toasts, sidebars, and overlays. It is the escape hatch that lets people recover from the current context. If it is hard to target on touch screens, if it blurs on high-density displays, or if it ignores motion and contrast preferences, it hurts the experience. A CSS-driven button keeps the bundle small, themes cleanly with variables, respects user settings, and avoids external icon fonts or images. You get pixel-perfect scaling, easy color control with currentColor, and zero network requests for assets.
Prerequisites
You will write a small amount of semantic HTML and focus most of the work in CSS. The CSS uses custom properties for theming and pseudo-elements for the “X” shape.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
The markup contains a demo panel and a single button element. The button is empty because the “X” icon will come from pseudo-elements. The accessible text lives in a visually-hidden span and is also provided as an aria-label for redundancy. In real apps, place this button inside the component that should close, such as a dialog header or a drawer.
Demo Content
This panel includes a close button anchored to the top-right corner. The icon is drawn with CSS, no images or SVG files involved.
Step 2: The Basic CSS & Styling
Set up a few variables for size, color, and stroke thickness. The button uses inline dimensions so it scales predictably. The demo panel gives us a target area for positioning. The “X” will inherit color from the button using currentColor. Add a strong focus-visible ring and a minimum hit size that meets mobile guidelines.
/* CSS */
:root {
--btn-size: 40px;
--stroke: 2px;
--fg: hsl(210 10% 20%);
--fg-hover: hsl(210 10% 10%);
--bg: transparent;
--bg-hover: hsl(210 20% 96%);
--ring: hsl(210 100% 45% / 0.35);
--radius: 8px;
}
/* Demo layout */
*,
*::before,
*::after { box-sizing: border-box; }
body {
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
margin: 0;
padding: 2rem;
color: hsl(210 10% 15%);
background: hsl(210 20% 98%);
}
.panel {
position: relative;
max-width: 520px;
margin-inline: auto;
padding: 1.25rem 1.25rem 2rem;
border-radius: 12px;
background: white;
box-shadow: 0 10px 30px hsl(210 15% 10% / 0.08);
}
.panel__title { margin: 0 0 .5rem; }
.panel__text { margin: 0; }
.visually-hidden {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* Base close button */
.close-btn {
inline-size: var(--btn-size);
block-size: var(--btn-size);
min-inline-size: 40px;
min-block-size: 40px;
display: inline-grid;
place-items: center;
border: 0;
border-radius: var(--radius);
background: var(--bg);
color: var(--fg);
cursor: pointer;
position: relative;
padding: 0;
line-height: 0;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
transition: background-color .18s ease, color .18s ease, transform .12s ease-out;
}
/* Anchor to top-right of the demo panel */
.panel .close-btn {
position: absolute;
inset-block-start: 10px;
inset-inline-end: 10px;
}
/* Focus styles */
.close-btn:focus-visible {
outline: 0;
box-shadow: 0 0 0 3px var(--ring);
}
.close-btn:hover { background: var(--bg-hover); color: var(--fg-hover); }
.close-btn:active { transform: scale(.96); }
Advanced Tip: Keep the button color on currentColor so themes can switch the “X” by only changing the text color. You can run a dark panel by toggling a class that flips color variables without touching the icon code.
Step 3: Building the “X” Icon
The “X” uses two bars drawn by pseudo-elements. Each bar is a short rectangle with rounded edges, centered with translate and rotated in opposite directions. The length is a fraction of the button size so it scales automatically.
/* CSS */
.close-btn::before,
.close-btn::after {
content: "";
position: absolute;
/* X bar size relative to button */
inline-size: var(--bar, calc(var(--btn-size) * .6));
block-size: var(--stroke);
background: currentColor;
border-radius: var(--stroke);
left: 50%;
top: 50%;
transform-origin: center;
/* First bar angled at 45deg */
transform: translate(-50%, -50%) rotate(45deg);
pointer-events: none; /* keep the bars from eating clicks */
}
/* Second bar angled at -45deg */
.close-btn::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
/* Optional thickness variants */
.close-btn--thin { --stroke: 1.5px; }
.close-btn--thick { --stroke: 3px; }
/* Dark theme helper (demo) */
.panel.dark {
background: hsl(220 15% 12%);
color: hsl(220 15% 90%);
box-shadow: 0 10px 30px hsl(220 15% 5% / 0.5);
}
.panel.dark .close-btn {
--fg: hsl(220 15% 85%);
--fg-hover: hsl(220 15% 96%);
--bg-hover: hsl(220 15% 20%);
--ring: hsl(210 100% 70% / 0.35);
}
How This Works (Code Breakdown)
The button is a square grid container sized by –btn-size, which gives us a predictable coordinate system for the icon. The pseudo-elements draw the two strokes of the “X” without extra markup. Using currentColor keeps the strokes in sync with the button color, so theme switches only change variables at the component boundary.
Centering uses left: 50% and top: 50%, followed by translate(-50%, -50%) to move the bar’s midpoint to the button’s center. The transform-origin stays on center, so rotation is symmetrical. One bar rotates by 45 degrees, the other by -45 degrees. The inline-size calculation ties stroke length to the button size: calc(var(–btn-size) * .6) keeps the arms inside the hit area even when the button grows.
Rounded edges on the bars (border-radius: var(–stroke)) improve legibility at small sizes. Setting pointer-events: none on the pseudo-elements avoids a situation where the thin bars block clicks that should hit the button. If you want a deeper primer on building the “X” shape alone, study the pattern in how to make an X icon close button with CSS and then return here to wrap it into a complete control.
Step 4: Background Variants and Placement
Some interfaces prefer a circular close button, others prefer a square tile. You can switch shapes by changing border-radius or by applying a modifier class. Hover backgrounds remain subtle to respect various themes. For layouts like dialogs, anchor the button to the top-right corner of the container.
/* CSS */
/* Circular button */
.close-btn--circle { border-radius: 50%; }
/* Square button with slight rounding */
.close-btn--square { border-radius: 8px; }
/* Filled and outline flavors */
.close-btn--solid { --bg: hsl(210 20% 96%); }
.close-btn--ghost { --bg: transparent; }
/* Hover states inherit variables defined earlier */
.close-btn--solid:hover { background: hsl(210 20% 94%); }
.close-btn--ghost:hover { background: var(--bg-hover); }
/* Placement helpers for component headers */
.header-close {
position: absolute;
inset-block-start: 8px;
inset-inline-end: 8px;
}
/* Optional larger target for touch-heavy UIs */
.close-btn--lg { --btn-size: 48px; --bar: calc(var(--btn-size) * .62); }
How This Works (Code Breakdown)
A circular version only needs border-radius: 50% on the button. If you want a round background elsewhere in your system, review the approach in how to make a circle with CSS, then reuse the same concept here. For square variants, a mild radius avoids sharp corners that can look harsh when the control is small; see a refresher on sizing with how to make a square with CSS if you want consistent tiles across a design system.
Filled and ghost styles shift only the background variables, leaving icon geometry untouched. The icon remains centered regardless of the shape. The placement helpers work well for dialog or card headers and keep the control a comfortable distance from edges.
Advanced Techniques: Animations and Microinteractions
Subtle motion can make the control feel responsive. Favor transform and opacity transitions for smooth performance. Apply a hover scale that snaps back on tap or click. If you add rotation, keep it small so it does not fight the intention to exit. Respect user motion preferences with a media query.
/* CSS */
/* Microinteraction: scale on hover/press and a tiny rotation */
.close-btn {
transition:
background-color .18s ease,
color .18s ease,
transform .12s ease-out;
}
.close-btn:hover {
transform: scale(1.06) rotate(0.0001deg); /* tiny rotate avoids text-resizing flickers in some browsers */
}
.close-btn:active {
transform: scale(.96);
}
/* Optional animated close: a quarter-turn when it appears */
@keyframes btn-pop {
from { transform: scale(.85) rotate(-8deg); opacity: 0; }
to { transform: scale(1) rotate(0); opacity: 1; }
}
.panel .close-btn {
animation: btn-pop .18s ease-out both;
}
/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
.close-btn,
.panel .close-btn {
transition: none;
animation: none;
}
}
Accessibility & Performance
A close button is simple in appearance but carries meaningful behavior. Treat it like any other critical control: semantic element, visible focus, large hit area, and clear labeling.
Accessibility
Use a real button element so you get keyboard support and semantics without extra work. Add aria-label to describe the action (“Close menu”, “Close dialog”, or “Close panel”) because the “X” alone is purely visual. The visually-hidden span provides a second source of text for assistive tech. Keep a minimum size of around 40-44px for touch targets. Ensure color pairs meet contrast guidance; a faint “X” on a light background can be hard to see. The :focus-visible ring in this guide stands out against both light and dark themes. If you include motion, honor prefers-reduced-motion so people can disable it.
Performance
This method is fast. Two absolutely positioned rectangles render with no external assets or layout complexity. Animations target transform and opacity, which are composited by the browser and do not trigger layout or paint on each frame. Avoid animating shadows or filter properties on hover if you want to keep interactions crisp on low-end hardware. Variables let you swap size and color without rewriting selectors, so the stylesheet stays compact.
Ship a Close Button That Feels Right
You now have a clean, scalable, and accessible Close button: a CSS-drawn “X”, shape variants, motion that respects user settings, and a focus ring you can trust. Wire this into your dialogs, drawers, and toasts, then theme it with a few variables. With this pattern in your toolkit, you can craft a consistent icon button set across your entire UI.