CSS: блок нестандартной формы с границей и скруглёнными углами

Нужно сверстать примерно такой блок (прозрачный с цветной границей):
введите сюда описание изображения

Сделал с помощью дополнительного дочернего блока и псевдоэлементов. Но скорее всего есть варианты лучше. Посоветуйте, плиз, что-то более изящное.

Проблема в том, что внутри будет контент - текстовой и визуальный. Поэтому просто нарисовать такую фигуру с помощью svg - не вариант. Может быть, как-то clip-path использовать или что-то ещё в этом роде?

.wrapper {
  width: 320px;
  height: 370px;
  border: 2px solid red;
  border-radius: 10px;
  position: relative;
}
.wrapper:before,
.wrapper:after {
  content: '';
  display: block;
  width: 15px;
  height: 15px;
  background-color: #fff;
  position: absolute;
  z-index: 2;
}
.wrapper:before {
  left: -5px;
  top: 160px;
}
.wrapper:after {
  left: 95px;
  bottom: -5px;
}
.border-block {
  background-color: #fff;
  position: absolute;
  width: 100px;
  height: 200px;
  border-top: 2px solid red;
  border-right: 2px solid red;
  border-left: 2px solid white;
  border-bottom: 2px solid white;
  border-top-right-radius: 10px;
  left: -2px;
  bottom: -2px;
}
.border-block:before,
.border-block:after {
  content: '';
  display: block;
  width: 15px;
  height: 15px;
  border-left: 2px solid red;
  border-bottom: 2px solid red;
  border-bottom-left-radius: 10px;
  position: absolute;
  z-index: 3;
}
.border-block:before {
  top: -17px;
  left: -2px;
}
.border-block:after {
  bottom: -2px;
  left: 100px;
}
<div class="wrapper">
  <div class="border-block"></div>
</div>


Ответы (5 шт):

Автор решения: Andrei Fedorov

Вам нужно изучить работу с масками в CSS, которые в основе имеют тот же принцип, что и CSS-градиенты.

  1. Итак, есть блок 300px x 300px, у которого нужно "отрезать" левый нижний угол 100px x 100px и все это со скруглениями 25px:

.box {
  width: 300px;
  aspect-ratio: 1;
  background-color: darkblue;
  border-radius: 25px;
}

body {
  display: grid;
  place-content: center;
  min-height: 100dvh;
  margin: 0;
}
<div class="box"></div>

  1. С помощью градиентов нужно нарисовать такую фигуру (любым цветом кроме прозрачного), чтобы она покрывала только видимые области финального блока. Прозрачные области фигуры будут в конечном итоге скрыты, такой принцип работы масок в CSS. Для наглядности я в начале нарисую фигуру с помощью градиентов и разными цветами, тогда сразу понятно какой градиент за какую часть отвечает:

Напоминание: background: <image> <position X> <position Y> / <size W> <size H>

.box {
  width: 300px;
  aspect-ratio: 1;
  background-color: darkblue;
  border-radius: 25px;
  
  background:
    linear-gradient(red, red) 0 0/100% calc(100% - 125px),
    linear-gradient(green, green) 125px 0/calc(100% - 125px) 100%,
    radial-gradient(circle at top right, blue 70%, #0000 72%) 100px 100%/25px 25px,
    radial-gradient(circle at top right, orange 70%, #0000 72%) 0 calc(100% - 100px)/25px 25px,
    radial-gradient(circle at bottom left, #0000 70%, black 72%) 75px calc(100% - 75px)/25px 25px,
    linear-gradient(darkcyan, darkcyan) 25px calc(100% - 100px)/100px 25px,
    linear-gradient(magenta, magenta) 100px calc(100% - 25px)/25px 75px;
  background-repeat: no-repeat;
}

body {
  display: grid;
  place-content: center;
  min-height: 100dvh;
  margin: 0;
}
<div class="box"></div>

  1. Теперь меняем background на mask (цвета не важны, лишь бы не было прозрачности):

.box {
  width: 300px;
  aspect-ratio: 1;
  background-color: darkblue;
  border-radius: 25px;
  
  mask:
    linear-gradient(red, red) 0 0/100% calc(100% - 125px),
    linear-gradient(green, green) 125px 0/calc(100% - 125px) 100%,
    radial-gradient(circle at top right, blue 70%, #0000 72%) 100px 100%/25px 25px,
    radial-gradient(circle at top right, orange 70%, #0000 72%) 0 calc(100% - 100px)/25px 25px,
    radial-gradient(circle at bottom left, #0000 70%, black 72%) 75px calc(100% - 75px)/25px 25px,
    linear-gradient(darkcyan, darkcyan) 25px calc(100% - 100px)/100px 25px,
    linear-gradient(magenta, magenta) 100px calc(100% - 25px)/25px 75px;
  mask-repeat: no-repeat;
}

body {
  display: grid;
  place-content: center;
  min-height: 100dvh;
  margin: 0;
}
<div class="box"></div>

ps: Решение только визуальное, оно не влияет на форматирование контента внутри блока. Т.е. никакого обтекания текстом не будет.

→ Ссылка
Автор решения: puffleeck

можно взять shape-image-threshold и связанные с ним свойства:

cell's: PC / touch moz o i.e'dge iOSx web view(safari) droid web view (chrome)
shape-outside 62 24 79 10.1 / 10.3 37
shape-image-threshold 62 24 79 10.1 / 10.3 (без %) 37
shape-margin 62 24 79 10.1 / 10.3 37
ну или clip-path 3.5 / 4 42 79 9.1 / 9.3 55

пример с кучей трафаретов:

#x{
  position: absolute;
  inset: 25px;
  border: 1px solid red;
  text-align: justify;
}

.y{
  shape-image-threshold: 0.2;
  width: 100%;
  height: 1.1em;
  transition: all ease .5s;
  shape-outside: linear-gradient(var(--to), transparent 70%, var(--shape));
  background: linear-gradient(var(--to), transparent 70%, var(--fill));
  margin: 0; padding: 0;
  --shape: var(--fill);
}
.y:nth-child(even){
  --fill: red;
  float: right;
  --to: 70deg;
}
.y:nth-child(odd){
  --fill: green;
  float: left;
  --to: -70deg;
}

#x:hover > .y:not(:nth-child(3n-2)){
  shape-image-threshold: 0.8;
  --fill: blue;
}
<div id='x'>
  <div class='y' title='блоки-трафареты должны быть ровесниками для блока с целевым текстом'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div class='y'></div>
  <div id='z'>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 
  </div>
</div>


пример с текстом без иерархически рАвного трафаретам, контейнера:

#x {
  position: absolute;
  inset: 10px;
  bottom: 4.5em;
  background: linear-gradient(lime, cyan);
  border: 3px solid blue;
  border-radius: 1em;
  padding: 1em;
  text-align: justify;
}

#y1 {
  --image: linear-gradient(90deg, red 4%, transparent 5%);
  border: 3px solid violet;
}

#y2{
  --image: linear-gradient(91deg, pink 50%, transparent 51%);
  border: 3px solid gold;
}
#y3{
  --image: radial-gradient(circle closest-side at 20px 10px, black 10px, violet 57px, transparent 150px);
  height: 10em!important;
}

#x>* {
  float: left;
  shape-image-threshold: 0.7;
  shape-margin: 5px;
  shape-outside: var(--image);
  background: var(--image);
  width: 100%;
  height: 50%;
  transition: all ease .5s;
}

#x:hover>* {
  shape-margin: 50px;
}

#x:hover> :first-child {
  background: none; /* отображение обтекаемой фигуры не требуется */
}
<div id='x'>
  <div id='y1'></div>
  <div id='y2'></div>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  <div id='y3' title='как на счет круга?'></div>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>


UPD 11.02.25: почти готово, осталось только габариты рамкам подправить

UPD: 12.02.25: габариты рамок пофиксил. разные цвета для наглядности

*{
  margin: 0;
  padding: 0;
}

#x {
  position: absolute;
  inset: 10px;
  font-size: 9vmin;
  border-radius: 2rem 2rem 2rem 0;
  box-shadow: inset -5px 0 0  violet;
  height: 90%;
}

#x:hover #y {
  shape-image-threshold: 0.1;
  shape-outside: linear-gradient(135deg, red, transparent);
}

#y, #z {
  float: left;
  height: calc(50% - 20px);
}

#y {
  shape-outside: linear-gradient(90deg, red, transparent);
  shape-image-threshold: .95;
  border: 5px solid green;
  border-bottom: none;
  border-radius: 2rem 2rem 0 2rem;
  transition: all ease 1s;
  width: calc(100% - 10px);
}

#z {
  display: inline-block;
  border: 5px solid blue;
  width: calc(50% - 10px - 1.5rem);
  margin: 0 1rem 0 2rem;
  border-radius: 0 2rem 0 0;
  border-left: none;
  border-bottom: none;
  position: relative;
}

#z:before,
#z:after {
  content: '';
  display: block;
  width: 2rem;
  height: 2rem;
  border-left: 5px solid red;
  border-bottom: 5px solid red;
  border-bottom-left-radius: 2rem;
  position: absolute;
  z-index: 3;
}

#z:before {
  top: -2.3rem;
  left: -2rem;
}

#z:after {
  bottom: -2.2rem;
  right: calc(-2rem - 100%);
  width: calc(100% + 1.5rem);
  border-right: 5px solid red;
  border-bottom-right-radius: 2rem;
}
<div id='x'>
  <div id='y'></div>
  <div id='z'></div>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
 exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
 dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
 mollit anim id est laborum.
</div>

→ Ссылка
Автор решения: Qwertiy

нужен прозрачный блок такой формы с границей

Кладём 4 div'а по сетке и немного подрезаем. Если радиус меньше половины размера каждой из областей, то можно не заморачиваться с вычислениями и использовать 50% для подрезки:

body {
  background: linear-gradient(to right, silver, white, silver, white, silver);
}

main {
  --bw: 3px;

  display: grid;
  grid-template: 5em 6em / 30fr 70fr;
  border-radius: 2em;
}

div {
  border: var(--bw) solid rgba(255, 0, 0, .5);
}

.lt {
  border-right: none;
  border-top-left-radius: inherit;
  border-bottom-left-radius: inherit;
  clip-path: polygon(50% 50%, 50% 100%, 0 100%, 0 0, 100% 0, 100% 50%);
}

.rt {
  border-left: none;
  border-bottom: 0;
  border-top-right-radius: inherit;
}

.rb {
  border-top: none;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
  clip-path: polygon(50% 50%, 50% 0, 100% 0, 100% 100%, 0 100%, 0 50%);
}

.lb {
  margin: calc(-1 * var(--bw));
  border-top-right-radius: inherit;
  clip-path: polygon(50% 50%, 50% 0, 100% 0, 100% 50%);
}
<main>
  <div class="lt"></div>
  <div class="rt"></div>
  <div class="lb"></div>
  <div class="rb"></div>
</main>

Если есть визуальные артефакты на стыках, а прозрачность границы не планируется и размеры позволяют, можно сделать небольшое наложение за счёт .lb:

body {
  background: linear-gradient(to right, silver, white, silver, white, silver);
}

main {
  --bw: 3px;

  display: grid;
  grid-template: 5em 6em / 30fr 70fr;
  border-radius: 2em;
}

div {
  border: var(--bw) solid red;
}

.lt {
  border-right: none;
  border-top-left-radius: inherit;
  border-bottom-left-radius: inherit;
  clip-path: polygon(50% 50%, 50% 100%, 0 100%, 0 0, 100% 0, 100% 50%);
}

.rt {
  border-left: none;
  border-bottom: 0;
  border-top-right-radius: inherit;
}

.rb {
  border-top: none;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
  clip-path: polygon(50% 50%, 50% 0, 100% 0, 100% 100%, 0 100%, 0 50%);
}

.lb {
  margin: calc(-1 * var(--bw));
  border-top-right-radius: inherit;
  clip-path: polygon(49% 51%, 49% 0, 100% 0, 100% 51%);
}
<main>
  <div class="lt"></div>
  <div class="rt"></div>
  <div class="lb"></div>
  <div class="rb"></div>
</main>

→ Ссылка
Автор решения: Qwertiy

Нашёл видео про эту форму: https://www.youtube.com/watch?v=khjVPkO35F0, в котором Kevin Powell предлагает такое решение: https://codepen.io/kevinpowell/pen/LYgMQpQ. В сниппете по сравнению с его кодом я изменил только ссылку на картинку и margin у body:

body {
  margin: 1rem 10rem;
  font-family: system-ui;
  font-size: 1.125rem;
}

img {
  display: block;
  max-width: 100%;
}

.house-info {
  --_br: 1rem;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: 1fr auto 1fr;
  max-width: 25rem;
  position: relative;
}

.house-image {
  grid-row: 1 / 3;
  grid-column: 1 / 3;
  border-radius: var(--_br);
  overflow: hidden;
}

.house-image::before,
.house-image::after,
.house-price::after {
  --_size: var(--_br);
  content: "";
  position: absolute;
  width: var(--_size);
  aspect-ratio: 1;
  background-image: radial-gradient(
    circle at 100% 100%,
    transparent var(--_size),
    var(--_corner-color, white) calc(var(--_size) + 1px)
  );
  rotate: -90deg;
}

.house-image::before {
  left: 0;
  bottom: 0;
  grid-column: 2;
  grid-row: 2 / 3;
}

.house-image::after {
  left: 0;
  bottom: 0;
  grid-column: 1;
  grid-row: 1 / 2;
  box-shadow: -1rem 0 0 white;
}

.house-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.house-price {
  display: grid;
  padding: 1rem 1rem 0 1rem;
  background: black;
  font-weight: 500;
  grid-row: 2 / 3;
  grid-column: 1 / 2;
  border-radius: var(--_br) var(--_br) 0 0;
  box-shadow: 0 1rem 0 black, 0 0 0 1rem white;
}

.house-price::after {
  --_size: calc(var(--_br) * 1.5);
  --_corner-color: black;
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  left: -1rem;
  bottom: -1rem;
}

.house-price span {
  display: grid;
  place-items: center;
  background: white;
  padding: 0.75rem 0;
  border-radius: calc(var(--_br) / 2);
}

.house-meta {
  margin: 0;
  background: black;
  color: white;
  grid-row: 3 / 4;
  grid-column: 1 / 3;
  font-size: 1.25rem;
  line-height: 1.8;
  padding-top: 1rem;
  border-radius: 0 var(--_br) var(--_br) var(--_br);
}
<div class="house-info">

  <div class="house-image">
    <img src='https://i.sstatic.net/rUtqN44k.jpg' alt=''>
  </div>

  <div class="house-price">
    <span> $500,000</span>
  </div>

  <ul class="house-meta">
    <li>Somewhere</li>
    <li>158m<sup>2</sup></li>
    <li>Big house</li>
  </ul>

</div>

→ Ссылка
Автор решения: New Line

Мое решение больше показывает суть ибо дорабатывать стоит по месту и задачам. Основная суть, сверху обычный блок с границами и скруглением, снизу таблица где левый табличный блок имеет блок для формирования внутренней рамки, в этот же блок я добавил бы пару скругленных блоков с position absolute, которые бы решили вопрос с пропусками в рамке на углах, ну а справа табличный блок с текстом и границами

<div style="
    border: 2px #000 solid;
    border-bottom: 0;
    min-height: 143px;
    border-radius: 25px 25px 0 25px;
    padding: 25px 25px 10px 25px;
">Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста Первая часть текста </div>
<div style="
    height: 300px;
    display: table;
    width: 100%;
    position: relative;
    table-layout: fixed;
"><div style="
    padding: 0 0 18px 18px;
    position: relative;
    width: auto;
    height: 100%;
    display: table-cell;
    vertical-align: top;
"><div style="
    border-top: 2px #000 solid;
    border-radius: 0 25px 0;
    border-right: 2px #000 solid;
    width: 100%;
    height: 100%;
    margin: -2px -2px auto auto;
"></div></div><div style="
    border: 2px #000 solid;
    border-top: none;
    border-left: none;
    border-radius: 0 0 25px 25px;
    padding: 10px 25px 25px 25px;
    width: auto;
    height: 100%;
    display: table-cell;
    vertical-align: top;
">Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста Вторая часть текста </div></div>

→ Ссылка