I would like to make a button like these one just with CSS without using another element.
Button Image
Since the button has a border attached, I think I normally need both, the :before and :after elements to create just one arrow at one side. So to make one arrow at each side I would need another span element inside the link.
The second method I tried is the one you see below. But with this solution they are not properly centered and each side of the arrow is different in length.
Has someone a solution?
/* General Button Style */
.button {
display: block;
position: relative;
background: #fff;
width: 300px;
height: 80px;
line-height: 80px;
text-align: center;
font-size: 20px;
text-decoration: none;
text-transform: uppercase;
color: #e04e5e;
margin: 40px auto;
font-family: Helvetica, Arial, sans-serif;
box-sizing: border-box;
}
/* Button Border Style */
.button.border {
border: 4px solid #e04e5e;
}
.button.border:hover {
background: #e04e5e;
color: #fff;
}
/* Button Ribbon-Outset Border Style */
.button.ribbon-outset.border:after,
.button.ribbon-outset.border:before {
top: 50%;
content: " ";
height: 43px;
width: 43px;
position: absolute;
pointer-events: none;
background: #fff;
}
.button.ribbon-outset.border:after {
left: -3px;
margin-top: -40px;
transform-origin: 0 0;
box-sizing: border-box;
border-bottom: 4px solid #e04e5e;
border-left: 4px solid #e04e5e;
transform: rotate(57.5deg) skew(30deg);
}
.button.ribbon-outset.border:before {
right: -46px;
margin-top: -40px;
transform-origin: 0 0;
box-sizing: border-box;
border-top: 4px solid #e04e5e;
border-right: 4px solid #e04e5e;
transform: rotate(57.5deg) skew(30deg);
}
.button.ribbon-outset.border:hover:after {
background: #e04e5e
}
.button.ribbon-outset.border:hover:before {
background: #e04e5e
}
Click me!
CodePen Demo
Here is another alternate way to get this done with only one element.
This approach works like below:
Two pseudo-elements :before and :after which are about half the size (including borders) of the main .button element. The height of each pseudo-element is 34px + 4px border on one side (top/bottom) and 2px on the other side.
The top half of the shape is achieved using the :before element whereas the bottom half is achieved using the :after element.
Using a rotateX with perspective to achieve the tilted effect and positioning to place the two elements such that they form the expected shape.
/* General Button Style */
.button {
position: relative;
display: block;
background: transparent;
width: 300px;
height: 80px;
line-height: 80px;
text-align: center;
font-size: 20px;
text-decoration: none;
text-transform: uppercase;
color: #e04e5e;
margin: 40px auto;
font-family: Helvetica, Arial, sans-serif;
box-sizing: border-box;
}
.button:before,
.button:after {
position: absolute;
content: '';
width: 300px;
left: 0px;
height: 34px;
z-index: -1;
}
.button:before {
transform: perspective(15px) rotateX(3deg);
}
.button:after {
top: 40px;
transform: perspective(15px) rotateX(-3deg);
}
/* Button Border Style */
.button.border:before,
.button.border:after {
border: 4px solid #e04e5e;
}
.button.border:before {
border-bottom: none; /* to prevent the border-line showing up in the middle of the shape */
}
.button.border:after {
border-top: none; /* to prevent the border-line showing up in the middle of the shape */
}
/* Button hover styles */
.button.border:hover:before,
.button.border:hover:after {
background: #e04e5e;
}
.button.border:hover {
color: #fff;
}
<!-- Library included to avoid browser prefixes -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
Click me!
Fixed Width Demo | Dynamic Width Demo
Output Screenshot:
This is tested in Chrome v24+, Firefox v19+, Opera v23+, Safari v5.1.7, IE v10.
As-is, this would degrade quite well in IE 8 and IE 9 into a square button with borders. However, due to the nullification of one border (border-bottom for :before and border-top for :after) it would leave a white area (resembling a strike-through line) in the middle. This can be overcome by adding a couple of IE < 10 specific styles using conditional comments like in this demo.
<!--[if IE]>
<style>
.button.border:after{
top: 38px;
}
.button.border:hover:before, .button.border:hover:after {
border-bottom: 4px solid #e04e5e;
}
</style>
<![endif]-->
Output Screenshot from IE 9 and IE 8:
This is just a simpler alternative to Harry's answer.
This approach uses scale() and rotate(45deg) transforms. Using this approach, you can very easily change the angle of right and left chevrons.
Fiddle
div {
height: 70px;
width: 200px;
margin-left: 40px;
border-top: 4px solid #E04E5E;
border-bottom: 4px solid #E04E5E;
position: relative;
text-align: center;
color: #E04E5E;
line-height: 70px;
font-size: 21px;
font-family: sans-serif;
}
div:before, div:after {
content:'';
position: absolute;
top: 13px;
height: 40px;
width: 40px;
border: 4px solid #E04E5E;
-webkit-transform: scale(0.8,1.25) rotate(45deg);
-moz-transform: scale(0.8,1.25) rotate(45deg);
-ms-transform: scale(0.8,1.25) rotate(45deg);
transform: scale(0.8,1.25) rotate(45deg);
}
div:before {
left: -22px;
border-top: 0px solid transparent;
border-right: 0px solid transparent;
}
div:after {
right: -22px;
border-bottom: 0px solid transparent;
border-left: 0px solid transparent;
}
div:hover, div:hover:before, div:hover:after {
background-color: #E04E5E;
color: #EEE;
}
<div>HELLO!</div>
Want a wider arrow? Simply decrease scale() transform's x value : Fiddle (wide)
Narrower one? Increase scale() transform's x value : Fiddle (narrow)
Note: IE 8 and below don't support CSS Transforms (9 supports with -ms- prefix), so you can check out this site for supporting them: IE Transform Translator
I forked your Pen
Codepen Demo
The essential changes are that I removed the side borders from the main button (as they were superflous
/* Button Border Style */
.button.border {
border-top: 4px solid #e04e5e;
border-bottom: 4px solid #e04e5e;
}
and changed a couple of values to tweak it all into place
/* Button Ribbon-Outset Border Style */
.button.ribbon-outset.border:after,
.button.ribbon-outset.border:before {
top: 50%;
content: " ";
height: 43px;
width: 43px;
position: absolute;
pointer-events: none;
}
.button.ribbon-outset.border:after {
left:0;
margin-top:-40px;
transform-origin:0 0;
box-sizing:border-box;
border-bottom:4px solid #e04e5e;
border-left:4px solid #e04e5e;
transform:rotate(57.5deg) skew(30deg);
}
.button.ribbon-outset.border:before {
right:-43px;
margin-top:-40px;
transform-origin:0 0;
box-sizing:border-box;
border-top:4px solid #e04e5e;
border-right:4px solid #e04e5e;
transform:rotate(57.5deg) skew(30deg);
}
I got the answer myself. It was a problem of the transform property of the :before and :after elements.
CSS changed:
/* Button Border Style */
.button.border {
border-top:4px solid #e04e5e;
border-bottom:4px solid #e04e5e;
}
/* Button Ribbon-Outset Border Style */
.button.ribbon-outset.border:after, .button.ribbon-outset.border:before {
height: 42px;
width: 42px;
}
.button.ribbon-outset.border:after {
left:0;
border-bottom:5px solid #e04e5e;
border-left:5px solid #e04e5e;
transform:rotate(45deg) skew(19deg,19deg);
}
.button.ribbon-outset.border:before {
right:-42px;
border-top:5px solid #e04e5e;
border-right:5px solid #e04e5e;
transform:rotate(45deg) skew(19deg,19deg);
}
Updated Codepen
Related
I am finding that applying a transform:skewX() to an element blurs its poition vertically(!) in Firefox. Does anyone know why, or how to fix this?
HTML
<div id="tab"><span>Tab</span></div>
<div id="content">Content</div>
CSS
#tab {
display: inline-block;
margin-left: 2em;
position: relative;
width: 3em;
text-align:center;
padding: .375em 0 0.125em;
}
#tab span {
position: relative;
z-index: 10;
}
#tab:before,
#tab:after {
position: absolute;
content: '';
top: 0;
width: 60%;
height: 100%;
border-top: 2px solid #999999;
background-color: #ffffff;
z-index: 1;
}
#tab:before {
left: 0;
transform: skewX(-20deg);
border-left: 2px solid #999999;
border-top-left-radius: 6px;
}
#tab:after {
right: 0;
transform: skewX(20deg);
border-right: 2px solid #999999;
border-top-right-radius: 6px;
}
#content {
border: 2px solid #999999;
border-radius: 6px;
padding: 4px;
margin-top: 0px;
}
The result is as below (zoomed image). Note the #content box's border showing through where they don't overlap, and the fuzziness at the top of the tab (both above and below the 2px width). By comparrison, when removing the skewX() for a straight tab, the border becomes pixel-perfect.
Is there any way to force the element's vertical pixel-alignment when it is subjected to a skewX() transform?
I've been trying some solutions found around the web, but they all are just a bit . . . off.
We have a design for a button's :focus style, like so:
That's 2px of empty space and a 2px outline.
Here's what we are doing now:
&::after {
border-radius: 18px;
content: "";
display: block;
margin: -2px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&:focus::after {
box-shadow: 0 0 0 2px blue;
}
which is almost good. See how it is just off-keel?
Is there a more reliable approach to this sort of design problem? Nudging the position properties by .5 pixels can hack it into shape but it just breaks elsewhere and makes my CSS bone ache.
you can try with border and background-clip:
button {
height: 50px;
border-radius: 25px;
width: 120px;
text-align: center;
color: #fff;
transition: .2s;
margin: 30px;
border: 3px solid transparent; /* we start transparent */
padding: 3px; /* control the space */
background: green content-box; /* color only the content */
}
button:hover {
border-color: blue; /* change color on hover */
}
body {
background:#f2f2f2;
}
<button>Accept</button>
I'm trying to absolute position an arrow relative to a button that toggles a filter box using :before pseudo-element.
I think I can't position relative to the filter box because its width is related to other div and I need the triangle to be aligned with the outside button.
Problem: when button is focused, the outline is present both in button and its :before, aka my triangle. I'm searching for a solution that outlines the button, but not its :before.
What I've already tried: set the rule outline: none; in the :before selector. Didn't worked.
What I've achieved:
My code, simplified for question purpose:
/* Toggle control isn't necessary for the purpose of the question */
.button-filter {
background-color: #333;
color: $click-white;
position: relative;
border: none;
color: #fff;
padding: 1em;
}
.button-filter:before {
content: "";
position: absolute;
display: block;
width: 0;
height: 0;
left: calc(50% - 15px);
bottom: -20px;
border-bottom: 15px solid #333;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
<button class="button-filter" autofocus>Filter</button>
You can change the outline with a box-shadowto avoid this effect:
.button-filter {
background-color: #333;
position: relative;
border: none;
color: #fff;
padding: 1em;
}
.button-filter:before {
content: "";
position: absolute;
display: block;
width: 0;
height: 0;
left: calc(50% - 15px);
bottom: -20px;
border-bottom: 15px solid #333;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
.button-filter:focus {
outline:none;
box-shadow:0 0 5px blue;
}
<button class="button-filter" autofocus>Filter</button>
Asuming that the pseudo will used to create an arrow next to another element, you don't really need it to be a triangle, it can be a rotated square. The lower part of it will be hidden by the other div.
If this is the case, the you can set the pseudo as a small square located inside the button. I set it red to show where it is, but in production you can set background transparent, or set z-index : -1;
And then set a shadow, located where you want the triangle to be. Since this is a shadow, not a real element, the outline won't be applied to it. (Note: the element is rotated, so the shadow coordinates are tricky)
/* Toggle control isn't necessary for the purpose of the question */
.button-filter {
background-color: #333;
color: $click-white;
position: relative;
border: none;
color: #fff;
padding: 1em;
}
.button-filter:after {
content: "";
position: absolute;
display: block;
width: 5px;
height: 5px;
left: 50%;
bottom: 10px;
transform: rotate(45deg);
background-color: red;
box-shadow: 40px 40px 0px 10px black;
}
<button class="button-filter" autofocus>Filter</button>
I need to create the button styles in the image below (the one on the right is transparent, not white).
The bottom right corner is obviously the tricky part. It's not just a simple bevel; it's slightly rounded.
The best solution I've come up with is to apply an SVG image mask to a pseudo element positioned to the right of the button and reduce the right padding to compensate. But this approach has its limitations:
it requires a fixed height button (at least, if I want maintain the aspect ratio of the corner)
it requires a different SVG for each button size
I don't see how it can work for the transparent button style
So I'm hoping someone can suggest a different/better approach!
Thanks
UPDATE:
Here is my current approach - https://codepen.io/peteheaney/pen/jwVEPm
$primary: #FAB500;
*, *::after, *::before {
font-family: sans-serif;
box-sizing: border-box;
}
.button {
background-image: none;
border-width: 2px;
border-style: solid;
border-color: transparent;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-bottom: 0;
text-align: center;
text-decoration: none;
text-transform: uppercase;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
transition: all 0.2s;
&:active,
&:hover,
&:focus {
text-decoration:none;
}
&--large {
font-size: 15px;
padding-left: 24.818px;
height: 52px;
line-height: 52px;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
position: relative;
margin-right: 24.818px;
&:after {
border-top: 2px solid $primary;
border-bottom: 2px solid $primary;
background: $primary;
content: "";
border-top-right-radius: 6px;
position: absolute;
left: 100%;
bottom: -2px;
width: 24.818px;
height: 52px;
mask: url(http://assets.peteheaney.com.s3.amazonaws.com/button-corner-right.svg) top left / cover;
}
}
&--primary {
color: #000;
background-color: $primary;
border-color: $primary;
&:active,
&:hover,
&:focus {
background-color: darken($primary, 2%);
border-color: darken($primary, 2%);
}
}
}
If you don't mind leaving the corner clickable, you could make the button invisible and just use a background image:
button{
width:x;
height:y;
border:none;
background-color:none
background-image:url(button_image.png);
background-position:center;
background-size:x y;
background-repeat:no-repeat;
}
With button_image.png being the image of your button style without text.
You can try to draw it like this using before and after :
.button {
position: relative;
display: inline-block;
background-color: orange;
color: white;
padding: 20px 40px;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
}
.button:after {
content: '';
position: absolute;
z-index: 10;
display: block;
bottom: -6px;
right: -2px;
width: 10px;
height: 20px;
transform: rotate(45deg);
background-color: white;
}
.button:before {
content: '';
position: absolute;
z-index: 100;
display: block;
bottom: -1px;
right: 4px;
width: 13px;
height: 23px;
transform: rotate(45deg);
background-color: orange;
border-radius: 10px;
}
Button
Here is an example of how this could possibly be achieved with pure CSS.
However an image or an SVG might be a more efficient way to solve this issue.
.Large{
position:relative;
display:inline-block;
background:#FFB300;
border:none;
padding:20px 0 20px 30px;
border-radius:10px 0 0 10px;
height:40px;
font:700 1.5em/40px Arial;
}
.Large::after{
content:"";
display:block;
position:absolute;
top:0;
right:-30px;
width:30px;
height:50px;
background:#FFB300;
border-radius:0 10px 0 0;
}
.Large::before{
content:"";
display:block;
position:absolute;
bottom:0;
right:-30px;
width:0;
height:0;
border-top: 15px solid #FFB300;
border-right: 15px solid transparent;
border-bottom: 15px solid transparent;
border-left: 15px solid #FFB300;
}
<a class="Large">LARGE</a>
I am not really happy with my result, but here it goes just in case you can make it better.
The different color is just to make it easier to see what is what.
I have focused on solving the transparent one. Once you have it, solving the other is easier.
:root {
--width: 10px;
--width2: 14px;
}
.test {
position: relative;
margin: 20px;
width: 300px;
height: 150px;
position: absolute;
border: var(--width) solid transparent;
border-image: linear-gradient(to bottom right, orange 0%, orange 70%, transparent 70%);
border-image-slice: 1;
}
.test:before {
content: "";
position: absolute;
height: 25px;
width: 150px;
right: 29px;
bottom: -10px;
transform: skewX(-45deg);
border: solid 0px transparent;
border-bottom-color: red;
border-bottom-width: var(--width);
border-right-color: red;
border-right-width: var(--width2);
border-bottom-right-radius: 25px;
}
.test:after {
content: "";
position: absolute;
height: 50px;
width: 25px;
right: -10px;
bottom: 29px;
transform: skewY(-45deg);
border: solid 0px transparent;
border-bottom-color: red;
border-bottom-width: var(--width2);
border-right-color: red;
border-right-width: var(--width);
border-bottom-right-radius: 25px;
}
<div class="test"></div>
I decided to go for the approach I have demonstrated in this pen - https://codepen.io/peteheaney/pen/bRBOMq (compiled CSS version below)
*, *::after, *::before {
font-family: sans-serif;
box-sizing: border-box;
}
.button {
background-image: none;
border-style: solid;
border-top-width: 2px;
border-bottom-width: 2px;
border-left-width: 2px;
border-right-width: 0;
border-color: transparent;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-bottom: 0;
text-align: center;
text-decoration: none;
text-transform: uppercase;
touch-action: manipulation;
vertical-align: middle;
white-space: normal;
transition: all 0.2s;
}
.button:active, .button:hover, .button:focus {
text-decoration: none;
}
.button--large {
font-size: 15px;
padding: 16px 0 14px 21px;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
position: relative;
margin-right: 21px;
}
.button--large:before {
content: "";
position: absolute;
left: 100%;
top: -2px;
width: 21px;
height: calc(100% - 17px);
border-top-right-radius: 6px;
}
.button--large:after {
position: absolute;
left: 100%;
bottom: -2px;
width: 21px;
height: 21px;
transition: all 0.2s;
}
.button--primary {
color: #000;
background-color: #FAB500;
border-color: #FAB500;
}
.button--primary:before {
background-color: #FAB500;
transition: all 0.2s;
}
.button--primary:active:before, .button--primary:hover:before, .button--primary:focus:before {
background-color: #f0ae00;
border-color: #f0ae00;
}
.button--primary:after {
content: url(http://assets.peteheaney.com.s3.amazonaws.com/button-corner-primary-large.svg);
}
.button--primary:active, .button--primary:hover, .button--primary:focus {
background-color: #f0ae00;
border-color: #f0ae00;
}
.button--secondary {
color: #000;
border-color: #FAB500;
}
.button--secondary:before {
border: 2px solid #FAB500;
border-bottom: 0;
border-left: 0;
transition: all 0.2s;
}
.button--secondary:active:before, .button--secondary:hover:before, .button--secondary:focus:before {
background-color: #FAB500;
}
.button--secondary:after {
content: url(http://assets.peteheaney.com.s3.amazonaws.com/button-corner-secondary-large.svg);
}
.button--secondary:active, .button--secondary:hover, .button--secondary:focus {
background-color: #FAB500;
border-color: #FAB500;
}
<a class="button button--large button--primary" href="">My button</a>
<a class="button button--large button--secondary" href="">My other button</a>
Firstly, I divided the right-hand portion into top and bottom (using :before and :after). The top-right pseudo element just has a background color and a top right border radius. This way the top-right portion can have a flexible height, meaning the buttons don't need to have a fixed height. The bottom right pseudo element is essentially an SVG ( using content: url(/path/to/svg.svg) ). This pseudo element always has a fixed width and height, so it maintains its size and aspect ratio regardless of the width/height of the button.
The outline style button is just a variation on the other style, with more borders and less backgrounds.
The only downside to this approach is the need for a different SVG for each button style. But I'm happy with that compromise.
Another take on Arthur's approach.
If you create the bottom right image (the white corner and the yellow corner border) you are able to position this so it stays to the bottom right and you have the rest of the button to style yourself.
button {
background-image:url(corner.svg);
height: 20px;
padding: 5px;
border-radius: 5px;
background-color: yellow;
background-repeat: no-repeat;
background-position: bottom right;
}
I am trying to create the button below by using the pseudo-elements before and after. I do not want to manipulate the HTML DOM. I'm searching for a solution with pure CSS.
How is it possible to make the currently white border-color of these triangles transparent?
//EDIT: The marked topic does not answer my question because I need a different angle than just rotating a square. It is also not transparent. I don't want to manipulate the DOM.
//SOLVED: This is the result after using Kate Millers solution:
//EDIT2: There is another problem with the solution I use now:
Is there a way to fix the border-width of the triangles (left and right)?
Here you can see how the size changes to 14.4 and 26.4px, 26.4px:
The best solution is to reverse the triangles (so you're adding top and bottom triangles that match the button, but not on the sides). You can technically make "transparent" triangles, but you can't have that transparency apply to a different object.
One of the most important things I changed was that the background color and padding of the button has to apply to the span element (which means each button will need an interior span), not .btn.
If you replace all of your CSS about the buttons with the below, you'll have a solution that gets you at least 90% of the way there. The angle isn't perfect because it's stopping at the text. If you want to make the angle truly perfect, you'll probably need to do some absolute positioning, which would make it messy as your button sizes change.
The non-code way you can also achieve this is to create a .png or .svg with triangles that match the color of your button and insert them into the :before and :after with content: ' ';
body { margin: 20px; background:#c2c2c2; }
.btn {
display: inline-block;
text-shadow: 0 1px 0 #000;
font-weight: bold;
text-transform: uppercase;
}
.btn {
padding: 11px 40px;
color: #fff;
position: relative;
background: #a00;
}
.btn:before, .btn:after {
content: '';
border-top: 20px solid #a00;
border-bottom: 20px solid #a00;
position: absolute;
top: 0;
}
.btn:before {
border-left:20px solid transparent;
left: -20px;
}
.btn:after {
border-right:20px solid transparent;
right:-20px;
}
.btn.inset:before, .btn.inset:after {
content: '';
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
position: absolute;
top: 0;
}
.btn.inset:before {
border-right:20px solid #a00;
left: -40px;
}
.btn.inset:after {
border-left:20px solid #a00;
right:-40px;
}
<div class="btn">Text in my little banner button</div>
<div class="btn inset">Text in my little banner button</div>
I don't use, and am not really familiar with, LESS... but you can create a similar element using a span and a wrapper with pseudo elements. It does take 2 triangles for each side (hence the span).
body { margin: 20px; background: #ddd; }
.btn {
display: inline-block;
padding: 11px 40px;
color: #fff;
position: relative;
background: #a00;
text-shadow: 0 1px 0 #000;
font-weight: bold;
text-transform: uppercase;
}
.btn:after,
.btn span:after,
.btn:before,
.btn span:before {
content: "";
width: 0;
height: 0;
display: block;
position: absolute;
right: 0;
border: 10px solid transparent;
}
.btn:after {
top: 0;
border-right-color: #ddd;
border-bottom-color: #ddd;
}
.btn span:after {
bottom: 0;
border-right-color: #ddd;
border-top-color: #ddd;
}
.btn:before {
top: 0;
left:0;
border-left-color: #ddd;
border-bottom-color: #ddd;
}
.btn span:before {
bottom: 0;
left:0;
border-left-color: #ddd;
border-top-color: #ddd;
}
<div class="btn"><span>Text in my little banner button</span></div>
I realize the ends aren't really transparent, they just match the background color to appear transparent.