Most CSS arrows end up on tooltips, dropdown menus and speech bubbles, where a small pointer shows what belongs to what. You can draw one from an empty box, a pseudo-element and a few declarations, so it scales and changes color with the page and never needs an image file.
The border triangle works because of how two adjoining borders meet. A 2025 CSS Working Group issue describes the join between two border colors as a line through the border-box corner and the padding-box corner.
Set width and height to 0, color one border and make the other two transparent, and a triangle is all that’s left.
What Are the Ways to Make CSS Arrows?
See the Pen
CSS Arrows for Real Websites by Bogdan Sandu (@bogdansandu)
on CodePen.
The usual options are a border triangle, a clip-path polygon, a rotated square, a Unicode character and an SVG. Most of them need nothing beyond plain CSS and one empty element.
| Method | Draws the arrow with | Best for | Main limit |
|---|---|---|---|
| Border triangle | Zero-size box, 3 sides colored or transparent | Tooltip tails, dropdown carets | Flat color only, no own border |
| clip-path polygon | polygon() points on a filled box | Scaling or gradient arrows | Clips the element’s own border |
| Rotated square | 2 borders on a square turned 45deg | Chevrons, accordion toggles | Open shape, no solid fill |
| Unicode character | An arrow glyph through content | Arrows inside running text | Look depends on the font |
| SVG | A path or polygon element | Curves, shafts, multi-color | Extra markup or a data URI |
No method wins everywhere. What decides it is the job the arrow has to do once it exists: take a gradient, animate, carry a border, or stay a few pixels wide.
For a small solid tail, I’d still start with the border triangle. Reach for clip-path when the arrow needs a gradient or has to move.
How Does the Border Triangle Method Work?
Give a box zero width and height plus thick solid borders, and the borders meet on diagonals. Color one border and make the other two transparent, and the visible part is a triangle. The colored border sits opposite the direction the tip points.
- Start with an empty element, or a pseudo-element with content set to an empty string.
- Set display to block or inline-block, because width and height do not apply to inline boxes.
- Set width and height to 0.
- Set border-style to solid. Give each side border half the arrow width and the base border the full arrow height.
- Make the two side borders transparent and color the base border.
Pointing the arrow in each direction
- For an arrow pointing up, color border-bottom and make border-left and border-right transparent.
- Pointing down, it’s border-top that gets the color, and the left and right borders stay transparent.
- Left needs border-right colored, with border-top and border-bottom cleared.
- Right works the other way around. Color border-left and make border-top and border-bottom transparent.
Setting size and color
The arrow height equals the width of the colored border. The arrow width equals the two transparent borders added together, so border-left and border-right of 10px each give a 20px base.
Leave the border color unset and it falls back to currentColor, which keeps the arrow in step with the text color of its parent.
When the arrow needs uneven proportions, a CSS triangle generator that outputs the border values saves the arithmetic.
The catch is the flat fill. A border triangle cannot take a gradient or carry its own border, so clip-path handles the first case and two stacked triangles handle the second.
How to Make a CSS Arrow With clip-path
Clip a filled box with polygon(), putting one point at the tip and two along the base. The background then fills the arrow, so the box can hold a solid color, a gradient or an image.
MDN lists clip-path as widely available, working across browsers since January 2020.
- Up uses
polygon(50% 0, 100% 100%, 0 100%) polygon(0 0, 100% 0, 50% 100%)points it down- For left, write
polygon(100% 0, 100% 100%, 0 50%) - And right is
polygon(0 0, 100% 50%, 0 100%)
Percentages scale with the box, so one rule fits any arrow size. For uneven proportions, a CSS clip-path generator outputs the points from a visual editor.
CodyHouse replaced the border hack with clip-path in its Tooltip component. The arrow now follows the tooltip’s background, and the tip can be rounded.
The same component wraps the rule in an @supports query for clip-path, so a browser without it gets a tooltip with no arrow instead of a broken one.
Polygon values animate, so an arrow can slide or morph between states, as long as both states keep the same number of points.
Support claims differ by source. A CSS triangle tutorial on pineco.de calls clip-path partially supported, while MDN’s Baseline data says otherwise. Check cross-browser compatibility in MDN’s table before trusting an undated tutorial.
It breaks in one place. clip-path hides everything outside the polygon, border and shadow included, so the arrow cannot carry an outline. MDN also notes that any clip-path other than none creates a new stacking context, the same way opacity below 1 does.
How to Build a Chevron or Double Arrow With Transform Rotate
Take an inline-block square, give it border-right and border-bottom in the arrow color, and rotate it 45 degrees. Only two borders show, so the corner becomes the tip.
With those two borders, rotate(45deg) points down, rotate(-45deg) points right, rotate(135deg) points left and rotate(-135deg) points up. Positive angles turn clockwise in left-to-right layouts.
Set box-sizing to border-box so the borders count toward the square’s size.
Rotation pivots on the element’s center by default, which keeps a chevron where it was placed. Set transform-origin to move the pivot when an arrow should swing like a hinge.
rotate() has worked across browsers since July 2015, according to MDN.
- A double arrow is two chevrons. The element draws one, and its ::after draws the second, offset along the pointing axis.
- For a line arrow, add a thin rectangle as the shaft, centered on the chevron’s axis, and let the chevron act as the head.
- Order matters when transforms combine. MDN shows that rotating before translating moves the translation along the rotated axis.
The downsides are an open shape and softer edges. A chevron takes no solid fill, and rotated borders are antialiased, so small chevrons look less crisp than an upright border triangle.
How to Attach an Arrow to a Tooltip, Dropdown or Speech Bubble
Draw the arrow on a ::before or ::after pseudo-element with position absolute, then offset it from the edge of the container that carries the pointer. The container needs position relative, or the arrow lands relative to some distant ancestor.
Centering and offsetting the arrow
For a tail below a box, set top to 100% and color the top border. Above it, bottom goes to 100% and the bottom border gets the color.
To center the tail horizontally, use left 50% plus transform translateX(-50%). A corner tail swaps the 50% for a fixed offset such as 16px. Side tails sit at left or right 100%, with top 50% and translateY(-50%).
Match the colored border to the tooltip background so the two read as one shape. Finished CSS tooltip examples show these offsets in working components.
Bordered arrows
The usual way to give an arrow a border is to stack two triangles. A larger arrow in the border color sits behind a slightly smaller arrow in the fill color, so a thin line of border color shows along both slanted edges.
Draw one with ::before and the other with ::after. Outlined tails in CSS speech bubble designs use the same pair.
A clip-path arrow cannot carry a border either, so the pair is the standard workaround there too.
Library and newer CSS options
Bootstrap 5.3 draws its tooltip arrow as a border triangle, 0.8rem wide and 0.4rem tall by default, with both values exposed as CSS custom properties (Bootstrap documentation).
- Popper.js handles the positioning, and Bootstrap’s tooltips depend on it.
- Floating UI has an arrow() middleware that returns coordinates keeping the arrow pointing at the center of the reference element.
- CSS Anchor Positioning tethers the tooltip to its anchor in pure CSS. MDN lists it as Baseline newly available since January 2026.
- border-shape draws the box outline with the arrow included, so border and shadow follow it.
Floating UI’s docs require the arrow element to be a child of the floating element, and arrow() belongs after shift() or autoPlacement() in the middleware array. Its FloatingArrow component draws an SVG arrow and needs a strokeWidth that matches (or is less than) the tooltip’s border width.
Early write-ups put border-shape behind a flag in Chrome Canary 146, but it has since shipped: Chrome 147 (April 2026) and Edge 147 support it by default, while Firefox and Safari do not. MDN still lists it as experimental and not Baseline.
For a plain tooltip tail, the border triangle still does the job. Move to clip-path or border-shape only when the arrow needs a gradient, a rounded tip or a shadow that follows its outline.
How to Animate a CSS Arrow
See the Pen
animated CSS arrows by Ed Tschoepe (@RenMan)
on CodePen.
Animate the arrow’s pseudo-element with transform or opacity, driven by a transition on hover or focus, or by a keyframe loop for scroll cues. Those two properties are also the cheapest ones a browser can animate.
- A hover nudge is translateX(4px) on a button’s arrow, with a short transition on transform.
- For a dropdown caret, rotate(180deg) flips it while the menu is open.
- A scroll cue is a keyframe loop that moves the arrow a few pixels down and back.
Hover nudges and caret flips are small micro-interactions, so keep the transition short and the distance small.
web.dev’s performance guidance names transform and opacity as the 2 properties a browser can animate without triggering layout or paint. A border triangle that grows through border-width changes its geometry, so scale it with transform instead.
For a scroll cue, define the movement with CSS keyframes and attach it through the animation shorthand with a finite iteration count.
The finite count is not cosmetic. WCAG Success Criterion 2.2.2 requires a way to pause, stop or hide moving content that starts automatically, lasts more than 5 seconds and sits beside other content (W3C).
An endless bounce fails that test, while a bounce that runs 3 times at 1 second each ends inside the limit.
Add a prefers-reduced-motion: reduce rule that sets animation to none. MDN lists the feature as Baseline widely available, and Chrome DevTools can emulate it from the Rendering tab (developer.chrome.com).
When Should You Use SVG, a Unicode Character or an Icon Font Instead?
Switch when the arrow needs a curve, a shaft of any length, several colors or a spot inside running text. A border triangle or clip-path polygon covers a straight pointer on a box and little else.
The Unicode Arrows block spans U+2190 to U+21FF, which is 112 code points (Unicode Consortium code chart). Insert one through an HTML entity such as → or a CSS escape like \2192 in the content property.
Size follows font-size and color follows the text color, which suits an arrow inside a sentence or a link.
SVG draws curved arrows, long shafts and multi-color shapes, and it scales without quality loss.
Placing SVG in HTML markup lets the arrow take its color from the surrounding text through currentColor, so it still follows the theme.
An icon font such as Font Awesome loads a whole font file to deliver one glyph here. It only pays off when the project already uses one.
Stick with CSS for a pointer attached to a box and move to SVG as soon as the shape needs a curve. Unicode fits the arrow sitting inside text.
Why Is a CSS Arrow Not Showing or Misaligned?
Most of the time the pseudo-element isn’t rendering at all, or the side borders aren’t transparent. Less often the parent is clipping the arrow or never positioned it.
| Symptom | Cause | Fix |
|---|---|---|
| No arrow at all | Pseudo-element has no content declaration | Set content to an empty string |
| Box instead of a triangle | Side borders are not transparent | Make the two side borders transparent |
| Arrow in the wrong place | Parent has no position value | Set position: relative on the container |
| Arrow cut off at the edge | Parent overflow hidden clips the overhang | Move the arrow inside or drop the clipping |
| Soft edges after centering | Odd width puts translateX(-50%) on a half pixel | Use an even arrow width |
Select the ::before or ::after node in the Elements panel of Chrome DevTools to read the arrow’s computed size and position before changing any value.
How to Keep CSS Arrows Accessible
Most CSS arrows are decorative, so screen readers shouldn’t announce them. Contrast and forced colors mode still need a check.
Hiding decorative arrows from screen readers
An arrow drawn with a border triangle or clip-path on an empty pseudo-element carries no text, so assistive technology has nothing to announce. A Unicode character is different.
MDN’s breadcrumb recipe warns that arrows added through the content property are exposed to screen readers and braille displays. An empty alternative text after a slash silences the glyph, as in content: "\2192" / "".
- Declare the plain content first and the slash version second, because a browser that cannot parse the slash syntax drops the whole declaration (Mozilla’s bug tracker).
- For an arrow character placed in markup, add aria-hidden=”true” to its span, since MDN lists decorative icons as the use case.
- Never put aria-hidden on a focusable element such as the button itself.
Arrow-only buttons, such as a carousel’s previous and next controls, need an accessible name. ARIA attributes handle it: aria-label on the button, aria-hidden on the icon inside.
Contrast against the background
WCAG 2.2 Success Criterion 1.4.11 sets a minimum contrast ratio of 3:1 against adjacent colors for user interface components and meaningful graphics (W3C).
A tooltip tail shares its color with the tooltip, so the page behind it is the background to test. A color contrast checker takes the two color values and returns the ratio.
An arrow that only repeats a visible text label is exempt, according to the DAISY Accessible Publishing Knowledge Base, so arrow-only controls carry the real risk.
Forced colors mode and transparent borders
Forced colors mode, usually triggered by Windows High Contrast, replaces authored colors with a system palette. Sources disagree on what that does to a border triangle.
cssence and the PatternFly high contrast handbook say transparent border colors are replaced with the text color, so the invisible sides turn the arrow into a box. modern-css.com describes the opposite: the transparent border stays invisible while the background is stripped.
Test instead of picking a side. Chrome DevTools emulates the mode under Rendering, with forced-colors: active selected (developer.chrome.com).
If a box appears, set forced-color-adjust to none on the arrow inside a forced-colors: active rule, keep the side borders transparent and color the pointing border with the CanvasText system color, the pattern cssence shows.
CSS arrow examples
Pure CSS3 arrow icons
See the Pen
Pure CSS3 arrow icons by Michael Evan (@thoughtleader)
on CodePen.
CSS Arrow Button

Mouse scroll Arrow animation
See the Pen
Mouse scroll animation by Yurij Rightblog.ru (@rightblog)
on CodePen.
Pure CSS Arrow

MENU to Close Arrow
See the Pen
MENU to Close Arrow by Joseph Fusco (@fusco)
on CodePen.
Arrow Animation CSS
See the Pen
Arrow @keyframes Animation by Carlo Flores (@carlodflores)
on CodePen.
Flipping CSS Arrows
See the Pen
Flipping Arrows by Sagee Conway (@saconway)
on CodePen.
CSS Animated Arrow Icon
See the Pen
CSS Animated Arrow Icon by Matt Braun (@mattbraun)
on CodePen.
Four levels of arrows

CSS Arrow Down Bouncing

Sliding Arrow CSS Animations

Animated – ‘Back to Top’ arrows

CSS Line Arrow

Message Box With Arrow (Transparent Background)

Simple pure Arrow Button

Arrow Animation
See the Pen
Arrow Animation by James Muspratt (@jmuspratt)
on CodePen.
Arrow (CSS transitions)

Gooey Scroll Arrow

An Arrow Always Point To A Certain Position

Animated CSS Arrows

Arrow Animate

CSS Cloud Uploader

CTA Arrow Hover Effect

Up Arrow

Bounce Scroll Down Arrow

Spinin’ load arrow

SVG Triple Arrow Animation

The Arrow

Scroll Down Arrow Animation

CSS Arrows FAQ
How do you make a curved CSS arrow?
Draw it as an SVG path, which handles curves, shafts and arrowheads in one element. clip-path also accepts shape() and path(), and MDN notes that shape() grows with the element while path() keeps its fixed coordinates.
Can a CSS arrow have a gradient fill?
Yes, but not with a border triangle. Give a box a linear-gradient background and clip it with polygon(). The gradient spans the whole box, so only the part inside the triangle shows.
How do you add arrows to a breadcrumb trail or a bulleted list?
Attach the arrow to each list item’s ::before or ::after with the content property and a Unicode arrow.
For a trail of breadcrumbs, use li:not(:last-child)::after so the final item carries no separator.
How do you replace the native arrow on a select menu?
Set appearance to none on the menu to drop the native arrow. Draw your own on a wrapper, since ::before and ::after do not work reliably on a select element itself, and add pointer-events: none so clicks reach the menu.
Are CSS arrows lighter than image arrows?
Yes. A CSS arrow is a few declarations inside a stylesheet the page already loads, so it adds no extra file request.
A PNG arrow needs its own file, plus another version for every color.
Which generators output CSS arrow code?
CSS Portal and CodeItBro both publish triangle generators that output border or clip-path code, and MDN offers a Shape generator for CSS shapes.
Use the border output for plain tooltip tails and the clip-path output when the arrow needs a gradient.
Choosing a Method for CSS Arrows in a Component Library
A component library should standardize on the border triangle for solid-fill pointers, with its color read from the same custom property as the tooltip background. When the theme changes, the arrow follows.
The order of work is simple. Bind the arrow color to the shared custom property, add a rotated square variant for bordered or shadowed tooltips, and test forced colors mode before release.
The order matters because a border triangle cannot carry a border or box-shadow of its own, and clip-path cuts both off at the polygon edge. The trade-off is two variants to maintain instead of one.
Verified in October 2026, this split holds until Firefox and Safari ship border-shape, which lets one declaration shape the box and its border together.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026









