ui
Collapse / Accordion
FAQ向けの折りたたみ。JS版推奨(内容高さが安定)。
accordionfaqcss+js
Customize
トークン: --mk-collapse-duration / --mk-collapse-ease
A11y
triggerに aria-expanded を付与。
Install
“壊れにくさ”優先のため、依存は増やさずに ファイルをコピーして使う 形式です。
Files
まずは下記ファイルをプロジェクトに配置してください。
required-files.md
- tailwind-motion-kit/css/00_tokens.css (css)
- tailwind-motion-kit/css/12_collapse.css (css)
- tailwind-motion-kit/js/collapse.js (js)Option A
まずは Bundle を読み込んで、動作を確認するのがおすすめです。
globals.css
/* Bundle: まずはこれが一番簡単 */
@import "./tailwind-motion-kit/css/99_bundle.css";Option B
必要な CSS だけ読み込みたい場合はこちら。
globals.css
/* 1) CSS: グローバルCSSに追記 */
@import "./tailwind-motion-kit/css/00_tokens.css";
@import "./tailwind-motion-kit/css/12_collapse.css";JavaScript
動きやUI制御が必要な場合のみ、初期化を追加します。
init.js
import { initCollapse } from "./tailwind-motion-kit/js/collapse.js";
initCollapse();Snippets
Copy & paste
usage.html
<div class="mk-collapse" data-mk-collapse>
<button class="mk-collapse-trigger" data-mk-collapse-trigger>Q</button>
<div class="mk-collapse-content" data-mk-collapse-content><div class="mk-collapse-inner">A</div></div>
</div>usage.js
import { initCollapse } from "./tailwind-motion-kit/js/collapse.js";
initCollapse();Files
この部品が参照する実ファイルです。
tailwind-motion-kit/css/00_tokens.csscss
tailwind-motion-kit/css/00_tokens.css
/*!
* Tailwind Motion Kit (mk) - Tokens
* --------------------------------
* 目的: 速度・距離・イージング等の“モーション言語”を統一し、
* パーツを増やしても品位が崩れない状態を作ります。
*
* 使い方:
* - :root の変数を上書きするだけで全体の動きを一括調整できます。
* - 個別要素は style="" か data-mk-* 属性で上書きできます(JSがある場合)。
*/
:root {
/* --- イージング(加減速) --- */
--mk-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--mk-ease-in: cubic-bezier(0.32, 0, 0.67, 0);
--mk-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
/* “少しだけ遊び”が欲しい時用(多用は上品さを損ねます) */
--mk-ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
/* --- 時間 --- */
--mk-duration-fast: 160ms;
--mk-duration-medium: 280ms;
--mk-duration-slow: 600ms;
--mk-duration-slower: 900ms;
/* コンポーネントが参照する“共通デフォルト” */
--mk-duration: var(--mk-duration-medium);
--mk-ease: var(--mk-ease-out);
--mk-delay: 0ms;
/* --- 移動量 --- */
--mk-distance-1: 6px;
--mk-distance-2: 12px;
--mk-distance-3: 20px;
--mk-distance: var(--mk-distance-2);
/* --- ぼかし --- */
--mk-blur-0: 0px;
--mk-blur-1: 6px;
--mk-blur-2: 10px;
--mk-blur: var(--mk-blur-1);
/* --- 影(浮遊感) --- */
--mk-shadow-lift: 0 12px 30px rgba(0, 0, 0, 0.14);
--mk-shadow-soft: 0 10px 22px rgba(0, 0, 0, 0.10);
/* --- アンダーライン --- */
--mk-underline-thickness: 2px;
--mk-underline-offset: 0.2em;
/* --- フォーカスリング --- */
--mk-ring-color: rgba(59, 130, 246, 0.35);
--mk-ring-size: 3px;
/* --- スケルトン(ロード中) --- */
--mk-skeleton-a: rgba(0, 0, 0, 0.06);
--mk-skeleton-b: rgba(0, 0, 0, 0.12);
--mk-skeleton-speed: 1250ms;
/* --- スピナー --- */
--mk-spinner-size: 1.2em;
--mk-spinner-border: 2px;
/* --- テキスト分割表示 --- */
--mk-char-step: 25ms; /* 1文字ごとの遅延 */
/* --- ダイアログ / ドロワー --- */
--mk-dialog-duration: var(--mk-duration-slow);
--mk-dialog-ease: var(--mk-ease-out);
--mk-dialog-backdrop-opacity: 0.45;
--mk-dialog-offset: var(--mk-distance-3);
--mk-dialog-scale-from: 0.995;
--mk-dialog-radius: 18px;
/* --- 折りたたみ(アコーディオン) --- */
--mk-collapse-duration: var(--mk-duration-medium);
--mk-collapse-ease: var(--mk-ease);
/* --- Toast / 通知 --- */
--mk-toast-gap: 10px;
--mk-toast-width: min(360px, calc(100vw - 24px));
--mk-toast-duration: var(--mk-duration-medium);
--mk-toast-ease: var(--mk-ease-out);
/* --- スクロール進捗(トップバー) --- */
--mk-scroll-progress-top: 0px;
--mk-scroll-progress-height: 3px;
--mk-scroll-progress-ease: linear;
/* --- ナビ下線インジケータ --- */
--mk-nav-underline-height: 2px;
--mk-nav-underline-radius: 999px;
--mk-nav-duration: var(--mk-duration-medium);
--mk-nav-ease: var(--mk-ease-out);
/* --- UI state(idle/loading/success/error) --- */
--mk-state-hold-success: 900ms;
--mk-state-hold-error: 1400ms;
/* --- Tabs --- */
--mk-tabs-gap: 10px;
--mk-tabs-radius: 999px;
--mk-tabs-duration: var(--mk-duration-medium);
--mk-tabs-ease: var(--mk-ease-out);
--mk-tabs-underline-height: 2px;
--mk-tabs-underline-color: currentColor;
/* --- Tooltip --- */
--mk-tooltip-bg: rgba(0, 0, 0, 0.82);
--mk-tooltip-fg: #fff;
--mk-tooltip-radius: 10px;
--mk-tooltip-padding: 10px 12px;
--mk-tooltip-gap: 8px;
--mk-tooltip-shadow: 0 18px 60px rgba(0,0,0,0.35);
--mk-tooltip-max-width: 260px;
/* --- Stepper --- */
--mk-stepper-gap: 14px;
--mk-step-size: 28px;
--mk-step-line: rgba(255, 255, 255, 0.14);
--mk-step-done: rgba(59, 130, 246, 1);
--mk-step-current: rgba(255, 255, 255, 0.92);
--mk-step-todo: rgba(255, 255, 255, 0.5);
}
/* motion減衰設定のあるユーザーには、動きを最小化して“結果”を出します */
@media (prefers-reduced-motion: reduce) {
:root {
--mk-duration-fast: 0ms;
--mk-duration-medium: 0ms;
--mk-duration-slow: 0ms;
--mk-duration-slower: 0ms;
--mk-duration: 0ms;
--mk-delay: 0ms;
}
}
tailwind-motion-kit/css/12_collapse.csscss
tailwind-motion-kit/css/12_collapse.css
/*!
* Tailwind Motion Kit (mk) - Collapse / Accordion
* ----------------------------------------------
* 目的: FAQ などの折りたたみを “上品に・破綻しない” 形で提供します。
*
* 推奨マークアップ:
* <div class="mk-collapse" data-mk-collapse>
* <button class="mk-collapse-trigger" data-mk-collapse-trigger aria-expanded="false">
* Question
* </button>
* <div class="mk-collapse-content" data-mk-collapse-content>
* <div class="mk-collapse-inner">
* Answer...
* </div>
* </div>
* </div>
*
* JS:
* import { initCollapse } from "./js/collapse.js";
* initCollapse();
*
* CSSだけで済ませたい場合:
* - root に mk-collapse-css を追加(grid trick)
* - ただし、内容が複雑な場合は JS 版が安定です
*/
.mk-collapse {
--mk-collapse-height: 0px;
}
.mk-collapse-trigger {
cursor: pointer;
}
.mk-collapse-content {
overflow: hidden;
max-height: 0px;
opacity: 0;
transition:
max-height var(--mk-collapse-duration) var(--mk-collapse-ease),
opacity var(--mk-collapse-duration) var(--mk-collapse-ease);
}
.mk-collapse[data-mk-open="true"] .mk-collapse-content {
max-height: var(--mk-collapse-height);
opacity: 1;
}
/* CSS-only variant(JS不要) */
.mk-collapse.mk-collapse-css .mk-collapse-content {
display: grid;
grid-template-rows: 0fr;
max-height: none;
opacity: 1;
transition: grid-template-rows var(--mk-collapse-duration) var(--mk-collapse-ease);
}
.mk-collapse.mk-collapse-css .mk-collapse-inner {
overflow: hidden;
}
.mk-collapse.mk-collapse-css[data-mk-open="true"] .mk-collapse-content {
grid-template-rows: 1fr;
}
/* reduce motion */
@media (prefers-reduced-motion: reduce) {
.mk-collapse-content {
transition: none !important;
}
}
tailwind-motion-kit/js/collapse.jsjs
tailwind-motion-kit/js/collapse.js
// Tailwind Motion Kit (mk) - Collapse / Accordion
// ----------------------------------------------
// data-mk-collapse を持つ要素を折りたたみとして初期化します。
// - trigger: [data-mk-collapse-trigger]
// - content: [data-mk-collapse-content]
// - open state: root[data-mk-open="true"]
// - group: data-mk-collapse-group="faq"(同じ値同士は1つだけ開く)
function prefersReducedMotion() {
return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function parts(root) {
const trigger =
root.querySelector("[data-mk-collapse-trigger]") ||
root.querySelector(".mk-collapse-trigger") ||
root.querySelector("button");
const content =
root.querySelector("[data-mk-collapse-content]") ||
root.querySelector(".mk-collapse-content");
return { trigger, content };
}
function setHeight(root, content) {
if (!content) return;
// scrollHeight は “見えていなくても” 内容の高さを返すので便利です
const h = content.scrollHeight;
root.style.setProperty("--mk-collapse-height", `${h}px`);
}
function setAria(trigger, open) {
if (!trigger) return;
trigger.setAttribute("aria-expanded", open ? "true" : "false");
}
export function openCollapse(root) {
if (!root) return;
const { trigger, content } = parts(root);
setHeight(root, content);
root.setAttribute("data-mk-open", "true");
setAria(trigger, true);
}
export function closeCollapse(root) {
if (!root) return;
const { trigger, content } = parts(root);
setHeight(root, content);
root.removeAttribute("data-mk-open");
setAria(trigger, false);
}
export function toggleCollapse(root) {
if (!root) return;
const open = root.getAttribute("data-mk-open") === "true";
if (open) closeCollapse(root);
else openCollapse(root);
}
function closeGroupSiblings(root) {
const group = root.getAttribute("data-mk-collapse-group");
if (!group) return;
document.querySelectorAll(`[data-mk-collapse][data-mk-collapse-group="${group}"]`).forEach((el) => {
if (el !== root) closeCollapse(el);
});
}
export function initCollapse({
selector = "[data-mk-collapse]",
} = {}) {
const reduce = prefersReducedMotion();
const initOne = (root) => {
const { trigger, content } = parts(root);
if (!trigger || !content) return;
// 初期高さ
setHeight(root, content);
// 初期aria
const open = root.getAttribute("data-mk-open") === "true";
setAria(trigger, open);
// click
const onClick = (e) => {
e.preventDefault();
const willOpen = root.getAttribute("data-mk-open") !== "true";
if (willOpen) closeGroupSiblings(root);
toggleCollapse(root);
};
trigger.addEventListener("click", onClick);
// resize / content change
const refresh = () => setHeight(root, content);
if (!reduce && "ResizeObserver" in window) {
const ro = new ResizeObserver(refresh);
ro.observe(content);
root.__mk_ro = ro;
} else {
window.addEventListener("resize", refresh);
root.__mk_onResize = refresh;
}
root.__mk_destroy = () => {
trigger.removeEventListener("click", onClick);
if (root.__mk_ro) root.__mk_ro.disconnect();
if (root.__mk_onResize) window.removeEventListener("resize", root.__mk_onResize);
};
};
document.querySelectorAll(selector).forEach(initOne);
return {
refresh() {
document.querySelectorAll(selector).forEach((root) => {
const { content } = parts(root);
if (content) setHeight(root, content);
});
},
destroy() {
document.querySelectorAll(selector).forEach((root) => {
if (typeof root.__mk_destroy === "function") root.__mk_destroy();
});
},
};
}
Preview
アコーディオンです。
グループ運用なら1つだけにできます。
Tips
- • デザイン側は CSS 変数(tokens)で一括調整できます。
- • 動きは “短く・小さく・同じ癖” を優先すると品位が保てます。
- • prefers-reduced-motion を必ず考慮してください。
