Building arrows in HTML is one of those tricks that seem simple, but hide much more depth than they appear to. Over time, experimenting with CSS, I've verified that most of the arrows we see on the web are not images, but the result of playing with borders, pseudo-elements, and a bit of positioning.
In this guide, I'm going to show you all the real ways to create arrows in HTML: from the simplest ones using HTML entities, to advanced arrows with pure CSS and SVG, explaining when to use each one.
Previously, we looked at how to create geometric shapes with CSS, which is the perfect foundation for understanding the technique we will use here.
What building arrows in HTML means
When we talk about building arrows in HTML, we don't just mean showing a → symbol on screen. In practice, arrows are used to:
- Indicate navigation directions
- Guide the user within the interface
- Complement containers like tooltips or speech bubbles
- Create forward/back buttons in carousels or galleries
- Reinforce visual hierarchy in menus and lists
The most common mistake is thinking that all arrows must be icons or images, when in reality HTML and CSS offer lighter, more flexible, and easier-to-maintain solutions.
What exactly do we mean when we talk about arrows in containers? Well, a picture is worth a thousand words:

It is a callout added to a container; they are ideal for pop-up menus or simply for building speech bubbles—text boxes. In short: placing arrows on a container with a bit of CSS, pointing to the right, left, down, or up, with some HTML and CSS.
Ways to create arrows in HTML (overview)
Before diving into detail, here are all the available options:
- HTML / Unicode entities → simple and quick for text
- Pure CSS → arrows without images using the
borderproperty - CSS arrows in containers → tooltips and speech bubbles with
::before/::after - SVG → complex or animated arrows with full vector control
- HTML + CSS + JavaScript → interactive and dynamic navigation
The key lies in choosing the right method according to context, not always using the most complex one.
Arrows using HTML entities (the simplest option)
HTML entities are the fastest way to add arrows directly into text. They work just like other well-known entities such as © or π: the browser interprets them and displays the corresponding character.
These are the most commonly used basic arrows—left, right, up, and down:
- Right arrow:
→→ - Left arrow:
←← - Up arrow:
↑↑ - Down arrow:
↓↓ - Double right arrow:
⇒⇒
You can also use their equivalent in decimal or hexadecimal Unicode code. For example, → is identical to → and produces the same result →. Both forms are valid and have universal support across all modern browsers.
HTML usage example:
<p>Move to the right → next step</p>This approach is ideal when you only need to indicate direction within text, without special styling or interaction.
When to use HTML entities and when not to?
Use them if:
- The arrow is part of the text flow
- You don't need custom styling or animations
- You are looking for maximum compatibility with minimal code
Avoid them if:
- You need to change size or color dynamically
- The arrow is part of a visual design or UI component
- It is integrated inside buttons, containers, or tooltips
That's where CSS starts to shine.
Building arrows with CSS using borders
Here we enter more interesting territory. When I started experimenting with CSS arrows, I realized I wasn't drawing arrows per se, but playing with invisible borders until one takes center stage and forms the desired triangle.
The border-width trick and transparent colors
The base is always the same: an element with width: 0 and height: 0, without content of its own, whose shape is defined entirely by its borders:
.flecha {
width: 0;
height: 0;
border-style: solid;
}From there you decide:
- Which border is visible (the one defining the arrow's direction)
- Which ones are
transparent(the ones that visually disappear) - The size of each border, which determines the arrow's proportions
Example: CSS arrow pointing to the right:
.flecha-derecha {
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 15px solid black;
}<div class="flecha-derecha"></div>In this case, border-top and border-bottom are transparent, creating the sides of the triangle, while border-left with color defines the visible tip of the arrow.
CSS arrows in four directions
By changing the visible border you can create a css arrow in any direction. The logic is counterintuitive at first, but it makes sense: the border you see is opposite to where the arrow points.
- Right arrow →
border-leftwith color - Left arrow →
border-rightwith color - Up arrow →
border-bottomwith color - Down arrow →
border-topwith color
This pattern is the foundation for most CSS arrows you will find in real production projects.
Common mistakes when creating arrows with CSS
From experience, these are the most common pitfalls when implementing this technique:
- Forgetting to set
width: 0andheight: 0on the element - Not declaring the other borders as
transparent, causing squares to appear instead of triangles - Using
position: relativeincorrectly when combining the arrow with a parent container - Miscalculating
border-widthsizes, which distorts the triangle's proportions
CSS arrows in containers and speech bubbles
For this CSS trick, we'll use a property we already covered in the previous post: Geometric shapes with CSS (part 2). In that post, we reviewed, through three examples, the versatility of the border-width property combined with pseudo-selectors ::before and ::after.
The trick here, just like in the mentioned post, is using the selectors ::before and/or ::after to create "another container" near the original container, giving it the desired shape with border-width and positioning it using top, left, and bottom properties.
The key is that these pseudo-elements must have content: "" (empty string) and position: absolute; the parent container, in turn, needs position: relative for absolute positioning to work correctly.
Let's see in detail how to create these arrows in containers.
Arrow in container: top and left
.arriba-izquierda:before {
content: "";
position: absolute;
top: -25px;
left: 0;
width: 0;
height: 0;
border-width: 0 25px 25px;
border-style: solid;
border-color: transparent transparent red;
}The pattern for each variant always follows the same structure:
- Pseudo-element
::beforeor::after position: absoluteand parent withposition: relative- Borders to create the triangular shape
Position adjustment with top, left, and bottom
If you analyze several examples, you'll see that the only thing that really changes is the position and the active border. In practice, moving an arrow is usually a matter of tweaking a few pixels in top, left, or bottom until it fits visually with the container.
Arrow in container: top and right
.arriba-derecha:before {
content: "";
position: absolute;
top: -25px;
left: 50px;
width: 0;
height: 0;
border-width: 0 25px 25px;
border-style: solid;
border-color: transparent transparent red;
}Arrow in container: right side and bottom
.lado-derecha-abajo:before {
content: "";
position: absolute;
top: 50px;
left: 100px;
width: 0;
height: 0;
border-width: 25px;
border-style: solid;
border-color: transparent transparent transparent red;
}Arrow in container: right side and top
.lado-derecha-arriba:before {
content: "";
position: absolute;
top: 0;
left: 100px;
width: 0;
height: 0;
border-width: 25px;
border-style: solid;
border-color: transparent transparent transparent red;
}Arrow in container: left side and bottom
.lado-izquierda-abajo:before {
content: "";
position: absolute;
top: 50px;
left: -50px;
width: 0;
height: 0;
border-width: 25px;
border-style: solid;
border-color: transparent red transparent transparent;
}Arrow in container: bottom and left
.abajo-izquierda:before {
content: "";
position: absolute;
bottom: -25px;
left: 0;
width: 0;
height: 0;
border-width: 25px 25px 0;
border-style: solid;
border-color: red transparent transparent;
}Arrow in container: bottom and right
.abajo-derecha:before {
content: "";
position: absolute;
bottom: -25px;
left: 50px;
width: 0;
height: 0;
border-width: 25px 25px 0;
border-style: solid;
border-color: red transparent transparent;
}If you review the previous CSS carefully, you'll notice that the only thing that varies between each variant is the pseudo-element's position—defined with top, bottom, and left—and the side where color is applied in border-color. That color is what "draws" the arrow; the other three sides remain transparent. That is how border-width and border-color work together to generate the desired shape.
We can also create arrows independent of containers, which are visually more appealing: more like actual arrows and less like simple triangles. They can be used in isolation, as if they were small arrow-shaped CSS icons:
The HTML consists of two nested blocks: a parent container with the class triangulo and direction, plus an empty inner div that will act as the arrow's "point":
<div class="triangulo abajo">
<div></div>
</div>The complete CSS is as follows:
.triangulo {
border-style: solid;
border-width: 18px;
width: 0;
height: 0;
}
.triangulo>div {
border-style: solid;
border-width: 10px;
width: 0;
height: 0;
position:relative;
}
.triangulo.derecha{
border-color: transparent transparent transparent black;
}
.triangulo.derecha>div {
border-color: transparent transparent transparent white;
top:-10px;
left:-21px;
}
.triangulo.izquierda{
border-color: transparent black transparent transparent;
}
.triangulo.izquierda>div {
border-color: transparent white transparent transparent;
top:-10px;
left:1px;
}
.triangulo.arriba{
border-color: black transparent transparent transparent;
}
.triangulo.arriba>div {
border-color: white transparent transparent transparent;
top:-21px;
left:-10px;
}
.triangulo.abajo{
border-color: transparent transparent black transparent;
}
.triangulo.abajo>div {
border-color: transparent transparent white transparent;
top:1px;
left:-10px;
}The operation is simple: the parent container (.triangulo) only has border—no width or height—defined solely with border-width. This makes it a basic CSS triangle.
Then, the inner div—a direct child of .triangulo—defines another smaller border-width and a strategic border-color. That color can have up to four values, one per side, corresponding to each possible arrow direction (right, left, up, or down):
border-color: transparent black transparent transparent;In the case above, we only defined color on the second value, which corresponds to border-right, creating the arrow pointing left. It is also possible to define more than one color per side to build larger arrows or arrows with different proportions.
Finally, to create that characteristic notch at the arrow's tail—and thus turn a simple triangle into a recognizable arrow—the inner container is positioned in the center of the shape with:
top: -10px; left: 1px;Creating more realistic arrows: from triangles to arrows
A triangle works, but often looks too flat. When I tried combining two overlapping triangles, the result was much closer to a real arrow and visually more polished.
CSS arrows with double container:
<div class="triangulo derecha">
<div></div>
</div>CSS (summary):
.triangulo {
border-style: solid;
border-width: 18px;
width: 0;
height: 0;
}
.triangulo > div {
border-style: solid;
border-width: 10px;
width: 0;
height: 0;
position: relative;
}How to simulate the "point" of an arrow
The second triangle, smaller and offset using top and left, creates that central cut-out that transforms a simple triangle into a visually appealing arrow. This approach is ideal when you need CSS icons without relying on external libraries like Font Awesome or similar.
Arrows with SVG: when it's worth using them
SVG makes sense when you need something that pure CSS cannot easily offer:
- Complex animations or curved paths
- Vector precision at any scale without loss of quality
- Reusability as a symbol in a
<defs>with<use>
For static or UI arrows, CSS is usually sufficient and more efficient.
HTML arrows for navigation and interaction
Arrows are often used as forward and back buttons in carousels, sliders, or paginations. For these cases, using <button> instead of a <div> is a good accessibility practice: it allows keyboard navigation and is recognized by screen readers.
Arrow as an accessible button:
<button aria-label="Anterior" class="btn-flecha"></button>Basic JavaScript usage to handle interaction:
document.getElementById("btn-siguiente").addEventListener("click", () => {
console.log("Siguiente");
});Here, the arrow is solely the visual part; the logic and behavior are provided by JavaScript. The aria-label attribute is essential for describing the button's action when there is no visible text.
Which method to choose for building HTML arrows
In summary, here is the quick guide to choosing the right approach depending on the use case:
- Simple text → HTML entities (
→,←…) - Lightweight design without images → CSS with
border - Tooltips, speech bubbles, and containers → pseudo-elements
::before/::after - Animations or complex graphics → SVG
- Interactive navigation → HTML + CSS + JavaScript with
<button>
There is no single correct solution, but rather the most suitable one for each usage context.
Frequently asked questions about arrows in HTML and CSS
- Can arrows be created without images?
- Yes, and in fact it is most recommended. With pure CSS or HTML entities you get scalable, lightweight, and easy-to-customize arrows without loading any external file.
- Which is better, CSS or SVG for arrows?
- CSS for simple interface arrows: buttons, tooltips, indicators. SVG for complex graphics, animated arrows, or when you need full vector precision.
- Do CSS arrows affect page performance?
- Not significantly. They are extremely lightweight compared to images or external library icons, since they don't generate any additional HTTP requests.
- Can I add a
css arrow with border(arrow with an outer border)?- Yes. The technique consists of overlapping two pseudo-elements (
::beforeand::after) slightly offset and with different colors: the first creates the border and the second, smaller one, simulates the inner fill of the arrow.
- Yes. The technique consists of overlapping two pseudo-elements (
Conclusion
Building arrows in HTML is an excellent example of how properly applied HTML and CSS can replace external images and icons. With a few borders, pseudo-elements, and some practice, you can create flexible, scalable, and seamlessly integrated CSS arrows in any web design, without depending on external libraries or adding unnecessary weight to the page.
The CSS triangle technique using border, combined with ::before and ::after, is one of those tools that once you master it, appears in dozens of different situations: tooltips, dropdown menus, position indicators, navigation buttons, and much more.
The next step: learn how to create a blurred lightbox with CSS.