06連携サービスグリッド
연결 버튼을 누르면 aria-pressed 가 바뀌며 점이 실제로 오른쪽으로 슬라이드되고 글자가 연결됨으로 바뀝니다.
使いどころ
aria-pressedtranslateXclick

<main class="stage">
<ul class="ig">
<li class="ig__tile">
<span class="ig__ic" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="m185 127l-58 58a24 24 0 0 1-33.94 0L71 162.91A24 24 0 0 1 71 129l58-58a24 24 0 0 1 33.94 0L185 93.09a24 24 0 0 1 0 33.91" opacity=".2"/><path d="M237.66 18.34a8 8 0 0 0-11.32 0l-52.4 52.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L100 88.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-52.41 52.4a8 8 0 0 0 11.32 11.32l52.4-52.41l5.37 5.38a32.06 32.06 0 0 0 45.26 0L156 167.31l6.34 6.35a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l52.41-52.4a8 8 0 0 0 0-11.32M121.37 190.06a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L100 121.31L144.69 166Zm57.94-57.94L156 144.69L111.31 100l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.63Z"/></g></svg></span>
<p class="ig__name">メール</p>
<button type="button" class="ig__toggle" aria-pressed="true"><span class="ig__track"><span class="ig__dot"></span></span><span class="ig__state">連携済み</span></button>
</li>
<li class="ig__tile">
<span class="ig__ic" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="m185 127l-58 58a24 24 0 0 1-33.94 0L71 162.91A24 24 0 0 1 71 129l58-58a24 24 0 0 1 33.94 0L185 93.09a24 24 0 0 1 0 33.91" opacity=".2"/><path d="M237.66 18.34a8 8 0 0 0-11.32 0l-52.4 52.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L100 88.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-52.41 52.4a8 8 0 0 0 11.32 11.32l52.4-52.41l5.37 5.38a32.06 32.06 0 0 0 45.26 0L156 167.31l6.34 6.35a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l52.41-52.4a8 8 0 0 0 0-11.32M121.37 190.06a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L100 121.31L144.69 166Zm57.94-57.94L156 144.69L111.31 100l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.63Z"/></g></svg></span>
<p class="ig__name">カレンダー</p>
<button type="button" class="ig__toggle" aria-pressed="false"><span class="ig__track"><span class="ig__dot"></span></span><span class="ig__state">連携する</span></button>
</li>
<li class="ig__tile">
<span class="ig__ic" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="m185 127l-58 58a24 24 0 0 1-33.94 0L71 162.91A24 24 0 0 1 71 129l58-58a24 24 0 0 1 33.94 0L185 93.09a24 24 0 0 1 0 33.91" opacity=".2"/><path d="M237.66 18.34a8 8 0 0 0-11.32 0l-52.4 52.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L100 88.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-52.41 52.4a8 8 0 0 0 11.32 11.32l52.4-52.41l5.37 5.38a32.06 32.06 0 0 0 45.26 0L156 167.31l6.34 6.35a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l52.41-52.4a8 8 0 0 0 0-11.32M121.37 190.06a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L100 121.31L144.69 166Zm57.94-57.94L156 144.69L111.31 100l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.63Z"/></g></svg></span>
<p class="ig__name">メッセンジャー</p>
<button type="button" class="ig__toggle" aria-pressed="false"><span class="ig__track"><span class="ig__dot"></span></span><span class="ig__state">連携する</span></button>
</li>
<li class="ig__tile">
<span class="ig__ic" aria-hidden="true"><svg viewBox="0 0 256 256" width="20" height="20"><g fill="currentColor"><path d="m185 127l-58 58a24 24 0 0 1-33.94 0L71 162.91A24 24 0 0 1 71 129l58-58a24 24 0 0 1 33.94 0L185 93.09a24 24 0 0 1 0 33.91" opacity=".2"/><path d="M237.66 18.34a8 8 0 0 0-11.32 0l-52.4 52.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L100 88.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-52.41 52.4a8 8 0 0 0 11.32 11.32l52.4-52.41l5.37 5.38a32.06 32.06 0 0 0 45.26 0L156 167.31l6.34 6.35a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l52.41-52.4a8 8 0 0 0 0-11.32M121.37 190.06a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L100 121.31L144.69 166Zm57.94-57.94L156 144.69L111.31 100l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.63Z"/></g></svg></span>
<p class="ig__name">スプレッドシート</p>
<button type="button" class="ig__toggle" aria-pressed="false"><span class="ig__track"><span class="ig__dot"></span></span><span class="ig__state">連携する</span></button>
</li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-quick;
$easing: $ease-spring;
$color: $subject-sky;
@include stage($stage-ink);
.ig { width: min(320px, 94vw); list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: $sp-3; }
.ig__tile {
display: flex; flex-direction: column; align-items: center; gap: $sp-1;
padding: $sp-3 $sp-2;
border-radius: $r-card;
background: rgba(255, 255, 255, .06);
border: 1px solid rgba(255, 255, 255, .1);
}
.ig__ic { display: grid; place-items: center; width: 30px; height: 30px; border-radius: $r-control; background: rgba(110, 200, 255, .18); color: $color; animation: ig-pulse $duration ease-in-out infinite alternate; }
@keyframes ig-pulse {
from { opacity: .75; }
to { opacity: 1; }
}
.ig__name { margin: 0; font-weight: 700; font-size: 11px; line-height: 1.2; font-family: inherit; color: $stage-paper; }
.ig__toggle {
display: inline-flex; align-items: center; gap: $sp-1;
padding: $sp-1 0;
border: 0; background: transparent;
color: rgba(255, 255, 255, .72);
font-weight: 700; font-size: 10px; line-height: 1;
cursor: pointer;
}
.ig__toggle[aria-pressed="true"] { color: $stage-paper; }
.ig__track {
position: relative; flex: 0 0 auto;
width: 26px; height: 14px; border-radius: $r-pill;
background: rgba(255, 255, 255, .22);
transition: background $duration $easing;
}
.ig__toggle[aria-pressed="true"] .ig__track { background: #50a26e; }
.ig__dot {
position: absolute; top: 1px; left: 1px;
width: 12px; height: 12px; border-radius: 50%;
background: $stage-paper;
transform: translateX(0);
transition: transform $duration $easing;
}
.ig__toggle[aria-pressed="true"] .ig__dot { transform: translateX(12px); }
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.ig__ic, .ig__toggle, .ig__dot { animation: none !important; transition: none !important; }
}
// ── 반응형(320×200) ──────────────────────────────────
@media (max-width: 340px), (max-height: 220px) {
.ig { width: min(260px, 92vw); gap: $sp-1; }
.ig__tile { padding: $sp-2 $sp-1; gap: $sp-1; }
.ig__ic { width: 20px; height: 20px; }
.ig__ic svg { width: 13px; height: 13px; }
.ig__name { font-size: 9px; }
.ig__toggle { font-size: 8px; gap: $sp-1; }
.ig__track { width: 20px; height: 11px; }
.ig__dot { width: 9px; height: 9px; }
.ig__toggle[aria-pressed="true"] .ig__dot { transform: translateX(9px); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ig {
width: min(320px, 94vw);
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.ig__tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 8px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.ig__ic {
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: 12px;
background: rgba(110, 200, 255, 0.18);
color: #6ec8ff;
animation: ig-pulse 220ms ease-in-out infinite alternate;
}
@keyframes ig-pulse {
from {
opacity: 0.75;
}
to {
opacity: 1;
}
}
.ig__name {
margin: 0;
font-weight: 700;
font-size: 11px;
line-height: 1.2;
font-family: inherit;
color: #ffffff;
}
.ig__toggle {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 0;
border: 0;
background: transparent;
color: rgba(255, 255, 255, 0.72);
font-weight: 700;
font-size: 10px;
line-height: 1;
cursor: pointer;
}
.ig__toggle[aria-pressed=true] {
color: #ffffff;
}
.ig__track {
position: relative;
flex: 0 0 auto;
width: 26px;
height: 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.22);
transition: background 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ig__toggle[aria-pressed=true] .ig__track {
background: #50a26e;
}
.ig__dot {
position: absolute;
top: 1px;
left: 1px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #ffffff;
transform: translateX(0);
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ig__toggle[aria-pressed=true] .ig__dot {
transform: translateX(12px);
}
@media (prefers-reduced-motion: reduce) {
.ig__ic, .ig__toggle, .ig__dot {
animation: none !important;
transition: none !important;
}
}
@media (max-width: 340px), (max-height: 220px) {
.ig {
width: min(260px, 92vw);
gap: 4px;
}
.ig__tile {
padding: 8px 4px;
gap: 4px;
}
.ig__ic {
width: 20px;
height: 20px;
}
.ig__ic svg {
width: 13px;
height: 13px;
}
.ig__name {
font-size: 9px;
}
.ig__toggle {
font-size: 8px;
gap: 4px;
}
.ig__track {
width: 20px;
height: 11px;
}
.ig__dot {
width: 9px;
height: 9px;
}
.ig__toggle[aria-pressed=true] .ig__dot {
transform: translateX(9px);
}
}// 실제 동작: "連携する" 버튼을 누르면 aria-pressed 가 바뀌며 점이 실제로 오른쪽으로 슬라이드되고 글자가 "連携済み"으로 바뀐다. 다시 누르면 되돌아간다.
document.querySelectorAll('.ig__toggle').forEach(function (btn) {
btn.addEventListener('click', function () {
var on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!on));
btn.querySelector('.ig__state').textContent = on ? '連携する' : '連携済み';
});
});import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./IntegrationGrid.module.scss";
export type IntegrationGridProps = { duration?: number; easing?: string; color?: string };
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const APPS = [
{ name: "이메일", connected: true },
{ name: "캘린더", connected: false },
{ name: "메신저", connected: false },
{ name: "스프레드시트", connected: false },
];
function PlugIcon() {
return (
<svg viewBox="0 0 256 256" width="20" height="20">
<g fill="currentColor">
<path d="m185 127l-58 58a24 24 0 0 1-33.94 0L71 162.91A24 24 0 0 1 71 129l58-58a24 24 0 0 1 33.94 0L185 93.09a24 24 0 0 1 0 33.91" opacity=".2" />
<path d="M237.66 18.34a8 8 0 0 0-11.32 0l-52.4 52.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L100 88.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-52.41 52.4a8 8 0 0 0 11.32 11.32l52.4-52.41l5.37 5.38a32.06 32.06 0 0 0 45.26 0L156 167.31l6.34 6.35a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l52.41-52.4a8 8 0 0 0 0-11.32M121.37 190.06a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L100 121.31L144.69 166Zm57.94-57.94L156 144.69L111.31 100l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.63Z" />
</g>
</svg>
);
}
export default function IntegrationGrid({ duration = 220, easing = "cubic-bezier(.2,.8,.2,1)", color = "#6ec8ff" }: IntegrationGridProps) {
const [apps, setApps] = useState(APPS);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
function toggle(i: number) {
setApps((prev) => prev.map((a, idx) => (idx === i ? { ...a, connected: !a.connected } : a)));
}
return (
<ul className={styles.ig} style={vars}>
{apps.map((a, i) => (
<li key={a.name} className={styles.tile}>
<span className={styles.ic} aria-hidden="true"><PlugIcon /></span>
<p className={styles.name}>{a.name}</p>
<button type="button" className={styles.toggle} aria-pressed={a.connected} onClick={() => toggle(i)}>
<span className={styles.track}><span className={styles.dot} /></span>
<span>{a.connected ? "연결됨" : "연결"}</span>
</button>
</li>
))}
</ul>
);
}06 連携サービスグリッド — 計測 累積面積 0.339% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · 【コピペOK】信頼を作るLPパーツ9選
このデモが属する記事
【コピペOK】信頼を作るLPパーツ9選LPパーツは、統計カウンターやロゴバーから比較表・問い合わせフォームまで、ランディングページのあちこちに散らばっていた信頼づくりの部品をひとまとめにしたコード集です。LPにそのまま貼れる九つのうち一つは、ページが開いた瞬間に数字が実際に0か この記事のデモをすべて見る


