We use a radial gradient and a conic gradient to create the rectangle shape with the rounded corner. Then another conic gradient for the bottom arrow. The first two gradients will cover only the padding area so that we can control the size of the last gradient using the border property.
Of course, we are not going to keep playing with gradients (I know it can be tricky) so we can add CSS variables to the game to easily control the shape.
.tooltip {
--r: 20px; /* radius */
--t: 20px; /* the arrow/tail size */
--a: 60deg; /* the angle of the tail*/
--p: 50%; /* the position */
--m:
conic-gradient(from calc(var(--a)/-2) at bottom,#0000,#000 1deg var(--a),#0000 calc(var(--a) + 1deg))
var(--p) 100%/100% 50% no-repeat,
conic-gradient(at var(--r) var(--r),#000 75%,#0000 0)
calc(var(--r)/-2) calc(var(--r)/-2) padding-box,
radial-gradient(50% 50%,#000 98%,#0000)
0 0/var(--r) var(--r) space padding-box;
border-bottom: var(--t) solid #0000;
-webkit-mask: var(--m);
mask: var(--m);
}
All you have to do is to adjust the different variables to get the result you want. Note that in this case, we have a variable to control the angle of the arrow. Using the clip-path method, we had the width and height but with the mask method, we have the height and the angle.
Let’s make the code for the other directions:
.tooltip {
--r: 20px; /* radius */
--t: 20px; /* the arrow/tail size */
--a: 60deg; /* the angle of the tail*/
--p: 50%; /* the position */
--_main:
conic-gradient(at var(--r) var(--r),#000 75%,#0000 0) calc(var(--r)/-2) calc(var(--r)/-2) padding-box,
radial-gradient(50% 50%,#000 98%,#0000) 0 0/var(--r) var(--r) space padding-box;
--_g: #0000,#000 1deg var(--a),#0000 calc(var(--a) + 1deg);
padding: calc(2*var(--r)/3);
-webkit-mask: var(--m);
mask: var(--m);
}
.top {
--m:
conic-gradient(from calc(180deg - var(--a)/2) at var(--p) 0%, var(--_g))
top/100% 50% no-repeat,
var(--_main);
border-top: var(--t) solid #0000;
}
.bottom {
--m:
conic-gradient(from calc(var(--a)/-2) at var(--p) 100%, var(--_g))
bottom/100% 50% no-repeat,
var(--_main);
border-bottom: var(--t) solid #0000;
}
.left {
--m:
conic-gradient(from calc(90deg - var(--a)/2) at 0% var(--p),var(--_g))
left/50% 100% no-repeat,
var(--_main);
border-left: var(--t) solid #0000;
}
.right {
--m:
conic-gradient(from calc(-90deg - var(--a)/2) at 100% var(--p),var(--_g))
right/50% 100% no-repeat,
var(--_main);
border-right: var(--t) solid #0000;
}
You can notice that even for the other direction we use the same gradients to create the rectangle shape. The only difference is the conic gradient that creates the arrow. For this reason, I created intermediate variables to avoid code repetition.