ShapeDivider
A component that renders a decorative border using SVG.
Overview
Lorem ipsum dolor sit amet. Consectetur adipiscing elit, …
<Stack> <p class="-p:30">Lorem ipsum dolor sit amet. Consectetur adipiscing elit, ...</p> <ShapeDivider viewBox="0 0 100 10"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z" /> </ShapeDivider></Stack><div class="l--stack"> <p class="-p:30">Lorem ipsum dolor sit amet. Consectetur adipiscing elit, ...</p> <div class="b--shapeDivider -max-sz:full" aria-hidden="true"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 100 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z"></path> </svg> </div> </div></div>Styles
The ShapeDivider base styles are defined in the following CSS.
/* Note: scale 反転は、__inner に対して行う。shapeDivider自身を反転させると、X方向に隙間ができてしまう。 */
@layer lism-block {
.b--shapeDivider {
--level: 5;
/* svg配置調整用 */
--inner-offset: 0px;
--inner-stretch: 1;
/* flip がオンなら -1 にセット */
--_flipX: 1;
--_flipY: 1;
/* 500px時点を最小値, 1200px時点を最大値にして、clampで高さを変動させる */
height: clamp(calc(5px * var(--level)), calc(1cqw * var(--level)), calc(12px * var(--level)));
overflow: visible;
}
.b--shapeDivider:where([data-flip^='X']) {
--_flipX: -1;
}
.b--shapeDivider:where([data-flip$='Y']) {
--_flipY: -1;
}
.b--shapeDivider_inner {
/* 隙間ができるのを防ぐ */
--offsetY: 0.5px;
--offsetX: -0.5px;
height: 100%;
width: calc(100% + 1px);
scale: calc(var(--_flipX) * var(--inner-stretch)) var(--_flipY);
translate: calc(var(--offsetX) + var(--inner-offset)) calc(var(--offsetY) * var(--_flipY));
}
.b--shapeDivider_svg {
/* 位置ずれ防止 */
display: block;
}
/* 左右にスライドするアニメーション */
:where([data-has-animation='true']) .b--shapeDivider_svg {
/* ↓ X 100% にしてるのは translateXのcalc計算で * -1 をしなくていいように。*/
transform-origin: 100% 0;
animation: lism--slideShape 12s infinite alternate linear;
}
}
@keyframes lism--slideShape {
0% {
transform: scaleX(4);
}
100% {
transform: scaleX(4) translateX(calc(100% - 25%));
}
} The full source code is available on GitHub.
Usage
Provided as ShapeDivider (b--shapeDivider) from the @lism-css/ui package.
Import
import { ShapeDivider } from '@lism-css/ui/react'; import { ShapeDivider } from '@lism-css/ui/astro'; <link href="https://cdn.jsdelivr.net/npm/@lism-css/ui@0.28.0/dist/style.css" rel="stylesheet" /> Props
| Prop | Description |
|---|---|
viewBox | The viewBox attribute for the output SVG. |
level | Specifies the height level of the shape as a positive number. Default is 5. |
offset | Outputs a variable to shift the shape horizontally. |
stretch | Outputs a variable to stretch the shape horizontally. |
flip | Specifies the flip direction of the shape: "X", "Y", or "XY". |
isEmpty | Renders no shape, allowing the component to be used as a spacer. |
isAnimation | Enables animation. Outputs data-has-animation="true". |
Examples
Placing shapes at the top and bottom of a section
Use flip to mirror the shape, allowing you to reuse the same SVG path for both the top and bottom dividers. (Separate paths can be provided instead if preferred.)
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.
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.
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.
<Wrapper hasGutter py="40"> <DummyText /></Wrapper><ShapeDivider viewBox="0 0 10 10" level="4"> <path d="M10,10V0L0,9.5V10L10,10z"></path></ShapeDivider><Wrapper hasGutter bgc="text" c="base"> <Box py="40"> <DummyText length="l" /> </Box></Wrapper><ShapeDivider viewBox="0 0 10 10" level="4" flip="XY"> <path d="M10,10V0L0,9.5V10L10,10z"></path></ShapeDivider><Wrapper hasGutter py="40"> <DummyText /></Wrapper><div class="is--wrapper has--gutter -py:40"> <p>...Contents...</p></div><div class="b--shapeDivider -max-sz:full" style="--level:4" aria-hidden="true"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 10 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M10,10V0L0,9.5V10L10,10z"></path> </svg> </div></div><div class="is--wrapper has--gutter -bgc:text -c:base"> <div class="l--box -py:40"> <p>...Contents...</p> </div></div><div class="b--shapeDivider -max-sz:full" style="--level:4" aria-hidden="true" data-flip="XY"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 10 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M10,10V0L0,9.5V10L10,10z"></path> </svg> </div></div><div class="is--wrapper has--gutter -py:40"> <p>...Contents...</p></div>Using an empty ShapeDivider as a spacer
You can use the component as a spacer by omitting the path and specifying isEmpty.
Even when empty, level still controls the height, making it useful for managing spacing on the side opposite a shape divider.
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.
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.
<Wrapper hasGutter bgc="base"> <ShapeDivider isEmpty level="5" /> <Box py="20"> <DummyText length="l" /> </Box> <ShapeDivider viewBox="0 0 10 10" level="5" > <path d="M10,10V0L0,9.5V10L10,10z"></path> </ShapeDivider></Wrapper><Wrapper hasGutter bgc="text" c="base"> <Box py="40"> <DummyText length="l" /> </Box></Wrapper><div class="is--wrapper has--gutter -bgc:base"> <div class="b--shapeDivider -max-sz:full" style="--level:5" aria-hidden="true"></div> <div class="l--box -py:20"> <p>...Contents...</p> </div> <div class="b--shapeDivider -max-sz:full" style="--level:5" aria-hidden="true"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 10 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M10,10V0L0,9.5V10L10,10z"></path> </svg> </div> </div></div><div class="is--wrapper has--gutter -bgc:text -c:base"> <div class="l--box -py:40"> <p>...Contents...</p> </div></div>Enabling animation
An example with animation enabled using isAnimation.

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.
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.
<Container pos="relative" hasGutter> <ShapeDivider isEmpty level="7" /> <Frame isLayer z="-1" max-w="100%"> <Media src="https://cdn.lism-css.com/img/a-1.jpg" alt="" width="960" height="640" /> <Layer bgc="black:50%" /> </Frame> <Wrapper contentSize="s" py="40" c="white"> <DummyText length="l" /> </Wrapper> <ShapeDivider viewBox="0 0 100 10" isAnimation c="base" level="7"> <path opacity=".3" d="M100,5.5C91.5,3,79.5,7.5,71.5,8C58,9,49,0.5,36,2c-9,1-13.5,3.5-20.5,3.5C8.5,6,0,2,0,2v8h100V5.5z"></path> <path opacity=".3" d="M100,1.5C87.8,6.6,79.3,2.1,67.8,2.6S53,6.5,42.5,5.5S23,0,15,0S1,3,0,4v6h100V1.5z"></path> <path d="M100,7.5C91.5,5,77.1,11.8,54,8C34.6,4.8,14.4,4.1,0,8.5V10h100V7.5z"></path> </ShapeDivider></Container><Container isWrapper="s" hasGutter bgc="base" py="40"> <DummyText length="l" /></Container><div class="is--container has--gutter -pos:relative"> <div class="b--shapeDivider -max-sz:full" style="--level:7" aria-hidden="true"></div> <div class="l--frame is--layer -z:-1 -max-w:100%"> <img src="https://cdn.lism-css.com/img/a-1.jpg" alt="" width="960" height="640" /> <div class="is--layer -bgc" style="--bgc:color-mix(in srgb, var(--black) 50%, transparent)"></div> </div> <div class="is--wrapper -contentSize:s -py:40 -c" style="--c:var(--white)"> <p>...Contents...</p> </div> <div class="b--shapeDivider -max-sz:full -c:base" style="--level:7" aria-hidden="true" data-has-animation="true"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 100 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path opacity=".3" d="M100,5.5C91.5,3,79.5,7.5,71.5,8C58,9,49,0.5,36,2c-9,1-13.5,3.5-20.5,3.5C8.5,6,0,2,0,2v8h100V5.5z"></path> <path opacity=".3" d="M100,1.5C87.8,6.6,79.3,2.1,67.8,2.6S53,6.5,42.5,5.5S23,0,15,0S1,3,0,4v6h100V1.5z"></path> <path d="M100,7.5C91.5,5,77.1,11.8,54,8C34.6,4.8,14.4,4.1,0,8.5V10h100V7.5z"></path> </svg> </div> </div></div><div class="is--container is--wrapper has--gutter -bgc:base -py:40 -contentSize:s"> <p>...Contents...</p></div>Adjusting position with stretch and offset
When reusing the same shape at both the top and bottom, simply flipping it may look slightly off.
In such cases, using stretch and offset to shift the SVG can produce a more natural result with the same shape.
stretch and offsetLorem 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.
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. Sed ut perspiciatis undeomnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.
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.
<Container isWrapper="s" hasGutter bgc="base" py="40"> <ShapeDivider isEmpty level="4" /> <DummyText length="l" /></Container><Container pos="relative" hasGutter c="base" bgc="text"> <ShapeDivider viewBox="0 0 100 10" c="base" flip="Y" level="8" stretch="1.1" offset="-5%"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z"></path> </ShapeDivider> <Wrapper contentSize="s" py="40"> <DummyText length="xl" /> </Wrapper> <ShapeDivider viewBox="0 0 100 10" c="base" level="8" stretch="1.1"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z"></path> </ShapeDivider></Container><Container isWrapper="s" hasGutter bgc="base" py="40"> <DummyText length="l" /> <ShapeDivider isEmpty level="4" /></Container><div class="is--container is--wrapper has--gutter -bgc:base -py:40 -contentSize:s"> <div class="b--shapeDivider -max-sz:full" style="--level:4" aria-hidden="true"></div> <p>...Contents...</p></div><div class="is--container has--gutter -pos:relative -c:base -bgc:text"> <div class="b--shapeDivider -max-sz:full -c:base" style="--level:8;--inner-offset:-5%;--inner-stretch:1.1" aria-hidden="true" data-flip="Y"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 100 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z"></path> </svg> </div> </div> <div class="is--wrapper -contentSize:s -py:40"> <p>...Contents...</p> </div> <div class="b--shapeDivider -max-sz:full -c:base" style="--level:8;--inner-stretch:1.1" aria-hidden="true"> <div class="b--shapeDivider_inner"> <svg class="b--shapeDivider_svg" viewBox="0 0 100 10" width="100%" height="100%" fill="currentColor" focusable="false" preserveAspectRatio="none"> <path d="M100 6C89.3 3.3 82.7 9 70 9S48.4 3 38 3 24.5 6 17 6C7.4 6 0 0 0 0v10h100V6z"></path> </svg> </div> </div></div><div class="is--container is--wrapper has--gutter -bgc:base -py:40 -contentSize:s"> <p>...Contents...</p> <div class="b--shapeDivider -max-sz:full" style="--level:4" aria-hidden="true"></div></div>