lukehillonline lukehillonline - 4 months ago 15
HTML Question

Give a CSS border arrow a border on 1 side

I am hoping someone can help me with trying to find a nice elegant way of getting a border on a CSS arrow border.

I am trying to create this:

enter image description here

Here is my code so far:

HTML

<div class="message-container customer">
<p class="message">Great thanks</p>
</div>


CSS

.message {
width: auto;
max-width: 66%;
padding: 12px 30px;
box-sizing: border-box;
background: #ffffff;
box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
margin: 4px 0 0 0;
position: relative;
float: right;
border-right: 4px solid #0892cb;
border-radius: 5px 0 0 5px;
}

.message:after {
top: 100%;
right: 0;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #FFFFFF;
border-width: 10px;
margin-right: -14px;
margin-top: -10px;
transform: rotate(45deg);
box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
}


Here is a working JS fiddle https://jsfiddle.net/kdeo3wpg/

As you can see I have a blue border on the right of he main message, but I cannot figure out a way to get the blue border on the arrow as well. If at all possible I would really like to avoid using an image. It would be great to find a CSS only solution.

I have thought about trying to use the
:before
sudo element but I can't get the full control I need.

Any help would be greatly appreciated.

UPDATE

I have managed to find a solution, but to be honest it is not very clean.

https://jsfiddle.net/kdeo3wpg/1/

What I have done is add a new element which is the width of the border and has the same background colour. I then set the height to slightly less than the height of the CSS arrow. I then give my new element a CSS arrow the background colour of the border.

Here is the new code:

HTML

<div class="message-container customer">
<p class="message">
Great thanks
<span class="arrow-border"></span>
</p>
</div>


CSS

.message {
width: auto;
max-width: 66%;
padding: 12px 30px;
box-sizing: border-box;
background: #ffffff;
box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
margin: 4px 0 0 0;
position: relative;
float: right;
border-right: 4px solid #0892cb;
border-radius: 5px 0 0 5px;
}

.message:after {
top: 100%;
right: 0;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #FFFFFF;
border-width: 10px;
margin-right: -14px;
margin-top: -10px;
transform: rotate(45deg);
box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
}

.arrow-border {
position: absolute;
background: #0892cb;
width: 4px;
height: 9px;
bottom: -9px;
right: -4px;
z-index: 1;
}

.arrow-border:after {
top: 100%;
right: 0;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #0892cb;
border-width: 3px;
margin-right: -3px;
margin-top: -3px;
transform: rotate(45deg);
box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
}


While this solution works, I feel there could be a better and cleaner options so I am still open to suggestions.

Answer

Using svg you could create your text bubble and apply linearGradient to it.

body {
  background: #eee;
}
<svg width="100" height="50" preserveAspectRatio="none" viewBox="-1 -1 102 52">
  <defs>
    <linearGradient id="grad">
      <stop offset="97%" stop-color="#fff" />
      <stop offset="97%" stop-color="#237ACB" />
    </linearGradient>
  </defs>
  <path d="M0,5 a5,5 0 0,1 5,-5 h95 v45 l-10,-10 h-85 a5,5 0 0,1 -5,-5" fill="url(#grad)" />
  <text x="50%" y="40%" text-anchor="middle" font-size="10">Great Thanks</text>
</svg>


For a text bubble to have dynamic text, you'll need to use the triangle as an svg. The text will be outside the svg.

body {
  background: #eee;
}
#container {
  position: relative;
  display: table;
}
#text {
  position: relative;
  max-width: 200px;
  padding: 10px;
  box-sizing: border-box;
  background: linear-gradient(to right, #fff calc(100% - 3px), #237ACB calc(100% - 3px));
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
}
#tri {
  position: absolute;
  z-index: 1;
  top: calc(100% - 1px);
  right: 0;
}
<div id="container">
  <svg id="tri" width="15" height="15" preserveAspectRatio="none" viewBox="0 0 15 15">
    <defs>
      <linearGradient id="grad">
        <stop offset="79%" stop-color="#fff" />
        <stop offset="79%" stop-color="#237ACB" />
      </linearGradient>
    </defs>
    <path d="M0,0 h15 v15 l-15,-15" fill="url(#grad)" />
  </svg>
  <div id="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>


Applying box-shadow:

To add a box-shadow to the svg you'll have to apply an svg filter on the path. Doing it through CSS won't work since CSS can't see the actual path.

feFuncA element's slope attribute controls the opacity of the shadow, feOffset is self explanatory.

body {
  background: #eee;
}
#container {
  position: relative;
  display: table;
}
#text {
  width: auto;
  max-width: 66%;
  padding: 12px 30px;
  box-sizing: border-box;
  background: #ffffff;
  box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
  margin: 4px 0 0 0;
  position: relative;
  float: right;
  border-right: 5px solid #0892cb;
  border-radius: 5px 0 0 5px;
}
#tri {
  position: absolute;
  z-index: 1;
  top: calc(100% - 1px);
  right: 0;
}
<div id="container">
  <svg id="tri" width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
    <defs>
      <linearGradient id="grad">
        <stop offset="70%" stop-color="#fff" />
        <stop offset="70%" stop-color="#0892cb" />
      </linearGradient>
      <filter id="shadow" height="130%">
        <feOffset dx="0" dy="2" in="SourceAlpha" result="offout" />
        <feComponentTransfer>
          <feFuncA type="linear" slope="0.1" />
        </feComponentTransfer>
        <feMerge>
          <feMergeNode/>
          <feMergeNode in="SourceGraphic" />
        </feMerge>
      </filter>
    </defs>
    <path d="M0,0 h15 v15 l-15,-15" filter="url(#shadow)" fill="url(#grad)" />
  </svg>
  <div id="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>


Reusing the svg:

To use the same svg path multiple times, you could define the path element within the defs element and use it multiple times using the use element as shown in the example below.

body {
  background: #eee;
}
.containerIn, .containerOut {
  position: relative;
  display: table;
  margin: 4px 0 15px;
}
.text {
  width: auto;
  max-width: 66%;
  padding: 12px 30px;
  box-sizing: border-box;
  background: #ffffff;
  box-shadow: 0 2px 0 2px rgba(177, 177, 177, .07), 0 2px 0 0 rgba(0, 0, 0, .1);
  margin: 4px 0 0 0;
  position: relative;
  float: right;
  border-right: 5px solid #0892cb;
  border-radius: 5px 0 0 5px;
}
.containerIn .text {
  border: 0;
  border-left: 5px solid #689F38;
  border-radius: 0 5px 5px 0;
  float: left;
}

.tri {
  position: absolute;
  z-index: 1;
  top: calc(100% - 1px);
  right: 0;
}
.containerIn .tri {
  left: 0;
}
<svg width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
  <defs>
    <linearGradient id="gradRight">
      <stop offset="70%" stop-color="#fff" />
      <stop offset="70%" stop-color="#0892cb" />
    </linearGradient>
    <linearGradient id="gradLeft">
      <stop offset="31%" stop-color="#689F38" />
      <stop offset="31%" stop-color="#fff" />
    </linearGradient>
    <filter id="shadow" height="130%">
      <feOffset dx="0" dy="2" in="SourceAlpha" result="offout" />
      <feComponentTransfer>
        <feFuncA type="linear" slope="0.1" />
      </feComponentTransfer>
      <feMerge>
        <feMergeNode/>
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
    <path id="triRight" d="M0,0 h15 v15z" filter="url(#shadow)" fill="url(#gradRight)" />
    <path id="triLeft" d="M0,0 v15 l15,-15z" filter="url(#shadow)" fill="url(#gradLeft)" />
  </defs>
</svg>

<div class="containerOut">
  <svg class="tri" width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
    <use xlink:href="#triRight" x="0" y="0" />
  </svg>
  <div class="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>

<div class="containerIn">
  <svg class="tri" width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
    <use xlink:href="#triLeft" x="0" y="0" />
  </svg>
  <div class="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>

<div class="containerIn">
  <svg class="tri" width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
    <use xlink:href="#triLeft" x="0" y="0" />
  </svg>
  <div class="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>

<div class="containerOut">
  <svg class="tri" width="15" height="18" preserveAspectRatio="none" viewBox="0 0 15 18">
    <use xlink:href="#triRight" x="0" y="0" />
  </svg>
  <div class="text">Text bubble that will change its width to <code>max-width</code>(200px) and height to contain the dynamic text</div>
</div>