How to Create a box with an angle one side and rounded corners via CSS?

How to Create a box with an angle one side and rounded corners via CSS?

11

So I currently find myself needing to make something like this.

My first thought was to use clip-path, but the rounded corners would be hard to pull off, and it would be hard to maintain the 22.5 degrees when the button changes width because of its contents.

So I ended up making each button 2 divs, with 1 div being skewed by 22.5 degrees and being overlapped by the regular rectangular div. Then I added border radius to both.

body {
  line-height: 0;
  font-size: 16px;
  background-color: black;
}

.cta-button-group {
  display: flex;
  gap: 2rem;
  align-items: center;
}

.button-angular-wrapper-left {
  display: flex;
  isolation: isolate;
  position: relative;
  height: 40px;
  width: fit-content;
}

.button-angular-wrapper-left .button-angular-main {
  border-radius: 7px 0 0 7px;
  height: 100%;
  display: inline-grid;
  place-items: center;
  padding-inline: 8px 16px;
  margin-right: 13px;
  transition: background-color 50ms;
}

.button-angular-wrapper-left .button-angular-slant {
  border-radius: 0 7px 7px 0;
  height: 100%;
  width: 24px;
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: -1;
  transition: background-color 50ms;
}

.button-angular-wrapper-left .button-angular-slant.back-slash {
  transform: skewX(22.5deg);
}

.button-angular-wrapper-left .button-angular-slant.forward-slash {
  transform: skewX(-22.5deg);
}

.button-angular-wrapper-left.button-angular-color-solid-white .button-angular-main,
.button-angular-wrapper-left.button-angular-color-solid-white .button-angular-slant {
  background: white;
  border: 3px solid white;
  color: blue;
}

.button-angular-wrapper-left.button-angular-color-solid-white .button-angular-main {
  border-right: none;
}

.button-angular-wrapper-left.button-angular-color-solid-white .button-angular-slant {
  border-left: none;
}

.button-angular-wrapper-right {
  display: flex;
  isolation: isolate;
  position: relative;
  height: 40px;
  width: fit-content;
}

.button-angular-wrapper-right .button-angular-main {
  border-radius: 0 7px 7px 0;
  height: 100%;
  display: inline-grid;
  place-items: center;
  padding-inline: 8px 16px;
  margin-left: 13px;
}

.button-angular-wrapper-right .button-angular-slant {
  border-radius: 7px 0 0 7px;
  height: 100%;
  width: 24px;
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  z-index: -1;
}

.button-angular-wrapper-right .button-angular-slant.back-slash {
  transform: skewX(22.5deg);
}

.button-angular-wrapper-right .button-angular-slant.forward-slash {
  transform: skewX(-22.5deg);
}

.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-main,
.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-slant {
  border: 3px solid white;
}

.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-main {
  border-left: none;
}

.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-main .icon-call {
  color: white;
}

.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-main .cta-text {
  color: white;
}

.button-angular-wrapper-right.button-angular-color-outline-white .button-angular-slant {
  border-right: none;
}
<div class="cta-button-group">
  <div class="button-angular-wrapper-left button-angular-color-solid-white" href="">
    <div class="button-angular-main">
      <span class="cta-text">
        Learn More Today
      </span>
    </div>
    <div class="button-angular-slant back-slash">
    </div>
  </div>
  <div class="button-angular-wrapper-right button-angular-color-outline-white" href="">
    <div class="button-angular-main">
      <span class="cta-text tel-link-no">
        1800-1-5555
      </span>
    </div>
    <div class="button-angular-slant back-slash">
    </div>
  </div>
</div>

Codepen: https://codepen.io/katylar/pen/yLRjKaO

It works, but it’s not perfect. I notice significant artifacts and weird corners/edges on some browsers at some resolutions.

Does anyone have a good solution? that doesn’t involved masks (which I always have a hard time with, sizing-wise)?

Share
Improve this question

New contributor

Exito is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

3

  • I think this might be possible with a pseudo element like :before

    – Apodemus

    yesterday

  • 1

    @Apodemus Pretty sure the OP knows about the existence of pseudo elements. The question is how you would use it.

    – rishi

    yesterday

  • I've posted an answer now with how I would have done it with pseudo elemetns

    – Apodemus

    yesterday

4 Answers
4

Reset to default

Highest score (default)

Trending (recent votes count more)

Date modified (newest first)

Date created (oldest first)

8

I’ve tried this approach with pseudo elements. The left side of this shape is a ::before element and for the hover effect I turned specific sides on the button and pseudo element invisible and also changed the border raduis of specific corners.

.button {
  color: white;
  background-color: black;
  text-align: center;
  text-transform: uppercase;
  padding: 5px 10px;
  margin: 10px;
  display: inline-block;
  border-radius: 4px;
  border: 2px solid black;
  -ms-transform: skewX(-20deg);
  -webkit-transform: skewX(-20deg);
  transform: skewX(-20deg);
}

.button-left::before {
  content: " ";
  display: block;
  position: absolute;
  top: -2px;
  left: -7px;
  z-index: -10;
  background-color: black;
  width: 20px;
  height: 100%;
  -ms-transform: skewX(20deg);
  -webkit-transform: skewX(20deg);
  transform: skewX(20deg);
  border-radius: 4px;
  border: 2px solid black;
}

.button-left:hover {
  background: rgba(0,0,0,0);
  box-sizing: border-box;
  border: 2px solid black;
  border-left: 2px solid rgba(0,0,0,0);
  color: black;
  border-top-left-radius: 0;
}

.button-left:hover::before {
  border-right: 2px solid rgba(0,0,0,0);
  background: rgba(0,0,0,0);
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.button-content {
  -ms-transform: skewX(20deg);
  -webkit-transform: skewX(20deg);
  transform: skewX(20deg);  
  display: inline-block;
}
<div class="button button-left">
  <span class="button-content">Slanted Button</span>
</div>

Share
Improve this answer

6

  • 1

    Unrelated to the answer but, you don't need to use rgba anymore, rgb accepts the same parameters (including the alpha value)

    – rishi

    yesterday

  • 1

    oh that's cool and it has wide support (except IE, but usage of IE is quite rare nowadays) too.

    – Apodemus

    yesterday

  • 3

    Technically, the pseudo-element is ::before, not :before. The spec says : are pseudo-classes, :: are pseudo-elements. Unfortunately, the spec didn’t originally make this distinction, and so most browsers accept both : and ::—for the original pseudo-elements. But for consistency with later pseudo-elements (which often must be ::), we should probably stick to the spec, unless you specifically need legacy browser support (I don’t know that IE ever got around to supporting ::).

    – KRyan

    21 hours ago

  • @KRyan I'll update it

    – Apodemus

    21 hours ago

  • 1

    Nice! To be clear, I’d already upvoted, but I appreciate the update.

    – KRyan

    21 hours ago

7

One suggestion, if you’re totally against masks, is to use perspective and rotate one div in 3D to give an angled element.

Use a container and make it display: inline-block so the angled div is always at the right hand side. That makes it, when you rotate it on the X axis, lean ‘backwards’ to the right. We create a new stacking context in the container to hide the last div below the parent. Use css custom properties to make sure the final div matches the width of the parent. Personally I’d use a mask but this could be used instead, certainly for elements with a solid background. I don’t know how you’d do it with border as the border width will also change with transform: rotateX() property. It’s also a bit fragile for varying heights amd widths but it might be a useful starting point.

Anyway, see what you think.

body {
  background: black;
  color: black;
}

.container {
  --height: 4rem;
  perspective: 100px;
  isolation: isolate;
  display: inline-flex;
}

.main,
.angle {
  display: inline-flex;
  align-items: center;
  padding-left: 1rem;
  background-color: white;
  border-radius: 0.5rem;
}

.main {
  height: var(--height);
  width: 200px;
}

.angle {
  position: relative;
  right: 30px;
  width: 30px;
  transform-origin: top center;
  transform: rotateX(20deg);
  height: calc(var(--height) * 0.96);
  z-index: -1;
}
<div class="container">
  <div class='main'>Learn more today!</div><div class="angle"></div>
</div>

Share
Improve this answer

3

This version does not require any extra HTML mark-up; it applies the appropriate slant effect automatically, using pseudo-elements. It also works with any number of buttons, and automatically adds slants only between elements, leaving the first element without a left slant and the last element without a right slant.

It also uses several CSS variables to allow it to be customized by a given use-case.

.slant-between:not(:first-child) {
  margin-top: 2rem;
}

.slant-between {
  white-space: nowrap;
  --border-width: 0.25rem;
  --border-radius: 0.5rem;
  --bg-color: black;
  --fg-color: white;
  --skew-angle: 25deg;
  --height: 2rem;
}

.slant-between>* {
  box-sizing: border-box;
  position: relative;
  z-index: 0;
  border: transparent;
  background: transparent;
  color: var(--fg-color);
  padding: 0 1rem;
  height: var(--height);
  line-height: var(--height);
  font-weight: bold;
  cursor: pointer;
}
.slant-between>:hover {
  color: var(--bg-color);
}
.slant-between>::before {
  content: ' ';
  position: absolute;
  z-index: -1;
  background: var(--bg-color);
  border: var(--border-width) solid var(--bg-color);
  border-radius: var(--border-radius);
  transform: skew(var(--skew-angle));
  top: 0;
  bottom: 0;
}
.slant-between>:not(:first-child)::before {
  left: 0;
}
.slant-between>:not(:last-child)::before {
  right: 0;
}
.slant-between>:hover::before {
  background: var(--fg-color);
}

.slant-between>:first-child::before,
.slant-between>:first-child::after,
.slant-between>:last-child::before,
.slant-between>:last-child::after {
  --skew-tangent: tan(var(--skew-angle));
  --abs-skew-tangent: max(var(--skew-tangent), -1 * var(--skew-tangent));
  --safe-overlap-width: calc(var(--height)/2 * var(--abs-skew-tangent) + var(--border-radius))
}
.slant-between>:first-child::before {
  left: var(--safe-overlap-width);
}
.slant-between>:last-child::before {
  right: var(--safe-overlap-width);
}
.slant-between>:first-child::before {
  border-left: 0;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.slant-between>:last-child::before {
  border-right: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.slant-between>:first-child::after, .slant-between>:last-child::after {
  content: ' ';
  position: absolute;
  z-index: -2;
  top: 0;
  bottom: 0;
  background: var(--bg-color);
  border: var(--border-width) solid var(--bg-color);
}
.slant-between>:first-child:hover::after, .slant-between>:last-child:hover::after {
  background: var(--fg-color);
}
.slant-between>:first-child::after {
  left: 0;
  right: var(--safe-overlap-width);
  border-right: 0;
  border-top-left-radius: var(--border-radius);
  border-bottom-left-radius: var(--border-radius);
}
.slant-between>:last-child::after {
  right: 0;
  left: var(--safe-overlap-width);
  border-left: 0;
  border-top-right-radius: var(--border-radius);
  border-bottom-right-radius: var(--border-radius);
}
<div class="slant-between">
  <button>Foo</button>
  <button>Bar</button>
  <button>Baz</button>
</div>

<div class="slant-between" style="--skew-angle: 15deg; --bg-color: green; --fg-color: blue; padding: 0.5rem; background: blue">
  <button>🜃</button>
  <button>🜂</button>
  <button>🜁</button>
  <button>🜄</button>
  <button>♥</button>
</div>

<div class="slant-between" style="--skew-angle: -45deg">
  <button style="--bg-color: pink">Hearts</button>
  <button style="--bg-color: yellow; --fg-color: black">Stars</button>
  <button style="--bg-color: purple">Horseshoes</button>
  <button style="--bg-color: green">Clovers</button>
  <button style="--bg-color: blue">Blue Moons</button>
  <button style="--bg-color: gold">Pots of Gold</button>
  <button style="--bg-color: linear-gradient(
        90deg,
        rgba(255, 0, 0, 1) 0%,
        rgba(255, 154, 0, 1) 10%,
        rgba(208, 222, 33, 1) 20%,
        rgba(79, 220, 74, 1) 30%,
        rgba(63, 218, 216, 1) 40%,
        rgba(47, 201, 226, 1) 50%,
        rgba(28, 127, 238, 1) 60%,
        rgba(95, 21, 242, 1) 70%,
        rgba(186, 12, 248, 1) 80%,
        rgba(251, 7, 217, 1) 90%,
        rgba(255, 0, 0, 1) 100%
    )">Rainbows</button>
  <button style="--bg-color: red">A Red Balloon</button>
</div>

It can handle a variety of skew angles, color schemes, border widths and radii, and so on.

The primary limitation of the code here is that you need to define the height of the buttons in the CSS, with the --height variable. In a lot of cases, all the trigonometry around --height can just be hard-coded with estimated values—there’s a lot of wiggle room on that.

It also doesn’t really work for backgrounds other than solid colors, as you can see with the “Rainbows” example, since you can’t use gradients or images or whatever for text or border colors. Still amused me to try it. I got the rainbow gradient from @Bartek’s answer.

Share
Improve this answer

4

  • Note that approach causes artifacts if the browser zoom is anything other than 100%.

    – Brian

    15 hours ago

  • Also causes artifacts on 4K monitors due to OS-level scaling.

    – RiverTam

    12 hours ago

  • @Brian Good point; overhauled the approach to address that, and added a bunch of variables for tweaking it.

    – KRyan

    8 hours ago

  • @RiverTam Good point; overhauled the approach to address that, and added a bunch of variables for tweaking it.

    – KRyan

    8 hours ago

0

This question made me think of Eric Meyer‘s slantastic in css/edge from 20 years ago.

Anyway, I created this approach while trying to resolve the glitch that causes the "artifacts" when zooming on KRyan’s original answer. I started out using a nested div, then span, but reverted to pseudo-elements because the results were identical.

I wanted to use relative lengths instead of absolute lengths and logical properties instead of physical properties. I wound up with an improvement in Chrome/Edge but not Firefox.

While I don’t think this is better than KRyan’s reworked answer, the other answers that currently have more upvotes fail to show the second part of the requested box, which is not as simple as reversing the first part (in my testing.)

.slanted {
  display: flex;
  justify-content: center;
  padding: 1em;
  background-color: #2a63b2;
  font-family: sans-serif;
}

.slanted div {
  position: relative;
  margin-inline: 1em;
  padding: .5em;
  white-space: nowrap;
  border: .25em solid white;
}

.slanted .first {
  color: #2a63b2;
  background-color: white;
  border-radius: .4em 1em 0 .4em;
  z-index: 0;
}

.slanted .second {
  padding-inline-start: 0;
  color: white;
  background-color: #2a63b2;
  border-inline-start-width: 0;
  border-radius: 0 .4em .4em .3em;
}

.slanted .first::after,
.slanted .second::before {
  content: ' ';
  position: absolute;
  top: -.25em;
  bottom: -.25em;
  width: 2em;
  border: .25em solid white;
  transform: skew(25deg);
}

.slanted .first::after {
  right: -.5em;
  border-radius: 0 .4em .4em 0;
  background-color: white;
  z-index: -1;
}

.slanted .second::before {
  left: -.7em;
  border-inline-end-width: 0;
  border-radius: .4em 0 0 .4em;
}

.slanted .second:hover {
  color: #2a63b2;
  background-color: white;
}

.slanted .second:hover::before {
  background-color: white;
  clip-path: polygon(0 0, 60% 0, 35% 50%, 35% 100%, 0 100%);
}
</style>
<div class="slanted">
  <div class="first">Learn more today!</div>
  <div class="second">1800-1-5555</div>
</div>

Share
Improve this answer

New contributor

Tim R is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Your Answer

Exito is a new contributor. Be nice, and check out our Code of Conduct.

Draft saved
Draft discarded

Post as a guest

Required, but never shown


By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged

or ask your own question.

Leave a Reply

Your email address will not be published. Required fields are marked *