Comments 커스터마이징
테마 토큰(색·폰트·모양) · feature on/off · 슬롯으로 댓글 위젯을 내 서비스에 맞추기.
댓글 위젯은 아무 옵션 없이도 기본 형태로 동작하고, 필요한 만큼만 단계적으로 커스텀합니다. 세 가지 계층이 있습니다.
appearance— 색·폰트·모서리·간격 토큰features— UI 요소 on/offslots— 특정 부분(또는 댓글 row 전체)을 직접 렌더
모두 init()(vanilla)과 @quipier/sdk/react(QuipierProvider/QuipierComments) 양쪽에서 동일하게 받습니다.
1. appearance — 테마 토큰
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
appearance: {
accent: "#4f46e5", // 버튼/강조
accentText: "#ffffff",
like: "#e11d48", // 좋아요(활성)
link: "#4f46e5", // 답글 토글 등
font: 'Inter, sans-serif',
fontSize: 15, // 숫자 → px
radius: 8, // 메뉴/입력
pillRadius: 8, // 버튼
avatarShape: "square", // "circle"(기본) | "rounded" | "square"
gap: 24, // 댓글 간 간격
},
});지정하지 않은 키는 기본값을 유지합니다. 색 키: accent accentText text muted surface border link like danger.
CSS로 직접 (토큰)
appearance는 결국 컨테이너에 --quipier-* CSS 변수를 꽂는 것이라, CSS로도 같은 결과를 낼 수 있습니다. 다크 모드는 theme: "dark" | "auto".
.my-comments .quipier-root {
--quipier-accent: #4f46e5;
--quipier-font: Inter, sans-serif;
--quipier-avatar-radius: 8px;
--quipier-gap: 24px;
}전체 토큰: --quipier-fg --quipier-muted --quipier-faint --quipier-surface --quipier-border --quipier-border-soft --quipier-hover --quipier-accent --quipier-accent-fg --quipier-link --quipier-like --quipier-danger --quipier-font --quipier-font-size --quipier-radius --quipier-radius-pill --quipier-avatar-radius --quipier-gap.
2. features — on/off
init({
// …
features: {
sort: false, // 정렬(필터) 버튼 숨김
badge: false, // "powered by Quipier" 숨김
likes: true,
replies: true, // false = 답글 비활성(= maxDepth 1)
report: true,
menu: true, // 댓글 ⋯ 메뉴
composer: true, // 입력창
avatars: true,
},
});모든 키 기본값은 true(현재 동작). 끄고 싶은 것만 false.
3. slots — 부분 또는 전체 교체
슬롯 함수는 HTMLElement | string | null | undefined(또는 Preact VNode)를 반환합니다. null/undefined를 반환하면 기본 렌더로 폴백하므로 조건부 커스텀이 가능합니다.
init({
// …
slots: {
// 댓글 row '전체' 교체 — view(데이터) + ctx(동작/헬퍼) 제공
comment: (view, ctx) => {
if (view.isDeleted) return null; // 삭제된 건 기본 처리
const el = document.createElement("div");
el.className = "my-comment";
el.textContent = `${view.author.nickname}: ${view.content}`;
const like = document.createElement("button");
like.textContent = `♥ ${view.likes.count}`;
like.onclick = () => ctx.actions.like(); // 동작은 ctx.actions로
el.append(like);
return el;
},
// 부분만 교체 — 나머지는 기본 row 유지(속성 상속)
avatar: (view) => `<img src="/u/${view.author.id}.png" width="32" />`,
authorLabel: (view) => `<b>${view.author.nickname}</b> <span class="vip">VIP</span>`,
},
});슬롯 종류
| 슬롯 | 인자 | 설명 |
|---|---|---|
header | (ctx) | 상단(개수 + 정렬) 전체 |
composer | (ctx) | 입력창 전체 |
empty | (ctx) | 댓글 0개일 때 |
comment | (view, ctx) | 댓글 row 전체 교체 |
avatar | (view, ctx) | 아바타만 |
authorLabel | (view, ctx) | 작성자 이름만 |
content | (view, ctx) | 본문만 |
actions | (view, ctx) | 좋아요/답글 줄만 |
comment(전체 교체)를 주면 그 안의 부분 슬롯(avatar 등)은 무시됩니다 — row 전체를 당신이 그리기 때문.
view (QuipierCommentView)
{
id, content, createdAt, // createdAt = ISO 문자열
author: { id, nickname, isOwn, blocked },
likes: { count, likedByMe },
isDeleted, isHidden, parentId, replyCount,
raw, // 원본 객체(가급적 의존 X)
}ctx
ctx.actions—like() unlike() reply(content) edit(content) remove() report(reason)(해당 댓글에 바인딩)ctx.helpers—formatTime(iso),avatarColor(seed)ctx.theme— 해석된--quipier-*값 맵ctx.defaultNode()— 기본 렌더 결과를 DOM 노드로 돌려줘 감싸기/장식에 사용
slots: {
comment: (view, ctx) => {
const wrap = document.createElement("div");
wrap.style.borderLeft = "3px solid #4f46e5";
wrap.appendChild(ctx.defaultNode()); // 기본 디자인 + 왼쪽 띠만 추가
return wrap;
},
}⚠️
defaultNode()는 스냅샷입니다. 장식(테두리·배지 추가)에는 적합하지만, 그 안의 기본 좋아요 버튼 상태는 위젯 본체와 동기화되지 않습니다. 완전한 동작 제어가 필요하면ctx.actions+view로 직접 렌더하세요.
CSS 훅 (data-part)
슬롯 없이 위치/스타일만 바꾸고 싶다면 모든 요소의 data-quipier-part를 CSS로 타겟하세요.
.quipier-root [data-quipier-part="actions"] { justify-content: flex-end; }
.quipier-root [data-quipier-part="avatar"] { box-shadow: 0 0 0 2px #4f46e5; }파트: root header count sort composer item body head author content actions like reply replies list empty loadmore error badge.
React
<QuipierProvider
config={{
apiKey: "qp_…",
projectId: "…",
appearance: { accent: "#4f46e5", avatarShape: "square" },
features: { sort: false },
}}
>
<QuipierComments pageId="/posts/1" />
</QuipierProvider>appearance/features/slots는 <QuipierComments>에서 인스턴스별로 덮어쓸 수도 있습니다.
예시 모음 (레시피)
복붙해서 바로 쓰는 완성형 예시입니다. 모두 init() 형태이며, React는 같은 객체를 QuipierProvider config/QuipierComments props로 넘기면 됩니다. 아래 미리보기는 실제 위젯이 데모 데이터로 렌더된 모습입니다(좋아요·작성은 패스포트 연결이 필요).
1) 브랜드 컬러 매칭
가장 흔한 케이스 — 강조색과 폰트만 우리 브랜드로.
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
appearance: {
accent: "#16a34a",
accentText: "#ffffff",
link: "#16a34a",
like: "#16a34a",
font: 'Pretendard, -apple-system, sans-serif',
radius: 10,
pillRadius: 10,
},
});2) 다크 카드 스타일
다크 테마 + 사이언 강조 + 컨테이너를 카드로(컨테이너 스타일은 호스트 CSS).
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
theme: "dark",
appearance: {
accent: "#22d3ee",
accentText: "#04222a",
surface: "#0b1020",
gap: 24,
},
});#comments {
background: #0b1020;
border: 1px solid #1e293b;
border-radius: 16px;
padding: 20px;
}3) 미니멀 / 컴팩트
정렬·배지·아바타를 끄고 간격을 좁혀 가볍게.
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
features: { sort: false, badge: false, avatars: false },
appearance: { gap: 14, fontSize: 13 },
});4) 읽기 전용 (작성·상호작용 끄기)
피드백을 받지 않고 보여주기만 할 때.
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
features: {
composer: false,
likes: false,
replies: false,
report: false,
menu: false,
},
});5) 커스텀 아바타 + 작성자 뱃지 (부분 슬롯)
아바타와 작성자 라벨만 우리 디자인으로 — 본문·좋아요·답글은 기본 그대로(상속).
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
slots: {
avatar: (view) => {
const d = document.createElement("div");
d.style.cssText =
"width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;background:linear-gradient(135deg,#f97316,#db2777)";
d.textContent = (view.author.nickname || "?").charAt(0).toUpperCase();
return d;
},
authorLabel: (view) =>
`<span style="font-weight:800">${view.author.nickname}</span>` +
`<span style="font-size:11px;background:#fce7f3;color:#db2777;padding:1px 6px;border-radius:999px;margin-left:6px">VIP</span>`,
},
});실제 프로필 이미지를 쓰려면
avatar에서<img src=…>를 반환하면 됩니다.
6) 인기 댓글 강조 (defaultNode 래핑)
기본 row를 그대로 쓰되, 좋아요가 많은 댓글만 띠+라벨로 강조.
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
slots: {
comment: (view, ctx) => {
if (view.likes.count < 5) return null; // 그 외는 기본 그대로
const wrap = document.createElement("div");
wrap.style.cssText =
"border-left:3px solid #f59e0b;padding-left:10px;background:#fffbeb;border-radius:6px";
const tag = document.createElement("div");
tag.textContent = "⭐ 인기 댓글";
tag.style.cssText = "font-size:11px;font-weight:700;color:#b45309;padding:4px 0";
wrap.append(tag, ctx.defaultNode()); // 기본 렌더 + 강조
return wrap;
},
},
});
defaultNode()는 스냅샷이라 장식용으로 적합합니다. 본문/좋아요 동작까지 직접 제어하려면 다음 예시(전체 교체)처럼view+ctx.actions로 그리세요.
7) 완전 커스텀 카드형 row (전체 교체)
row를 처음부터 우리 디자인으로. 동작은 ctx.actions로 연결.
init({
container: "#comments",
apiKey: "qp_…",
projectId: "…",
slots: {
comment: (view, ctx) => {
if (view.isDeleted) return null; // 삭제는 기본 tombstone
const card = document.createElement("article");
card.style.cssText =
"border:1px solid #e5e7eb;border-radius:12px;padding:14px;display:flex;flex-direction:column;gap:8px";
const head = document.createElement("div");
head.style.cssText = "display:flex;justify-content:space-between;align-items:center";
head.innerHTML =
`<strong style="font-size:13px">${view.author.nickname ?? "익명"}</strong>` +
`<time style="color:#9ca3af;font-size:12px">${ctx.helpers.formatTime(view.createdAt)}</time>`;
const body = document.createElement("p");
body.textContent = view.content;
body.style.cssText = "margin:0;font-size:14px;line-height:1.55";
const like = document.createElement("button");
like.textContent = `${view.likes.likedByMe ? "♥" : "♡"} ${view.likes.count}`;
like.style.cssText =
"align-self:flex-start;border:none;background:#f3f4f6;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer";
like.onclick = () =>
view.likes.likedByMe ? ctx.actions.unlike() : ctx.actions.like();
card.append(head, body, like);
return card;
},
},
});8) CSS만으로 재배치 (슬롯 없이)
JS 슬롯 없이 위치/스타일만 손볼 땐 data-quipier-part를 타겟.
/* 액션(좋아요·답글)을 오른쪽 정렬 */
#comments [data-quipier-part="actions"] { justify-content: flex-end; }
/* 아바타에 링 */
#comments [data-quipier-part="avatar"] { box-shadow: 0 0 0 2px var(--quipier-accent); }
/* 본문 폰트만 키우기 */
#comments [data-quipier-part="content"] { font-size: 15px; }9) React에서 인스턴스별 오버라이드
QuipierProvider에 공통 테마를 두고, 특정 위젯만 다르게.
<QuipierProvider config={{ apiKey: "qp_…", projectId: "…", appearance: { accent: "#16a34a" } }}>
{/* 본문용 — 공통 테마 */}
<QuipierComments pageId="/posts/1" />
{/* 사이드바용 — 컴팩트 + 정렬/배지 off */}
<QuipierComments
pageId="/posts/1#sidebar"
features={{ sort: false, badge: false }}
appearance={{ gap: 12, fontSize: 13 }}
/>
</QuipierProvider>React Native (@quipier/react-native)
모바일 SDK도 같은 appearance / features / slots 를 받습니다. 차이는 두 가지:
appearance는 CSS 변수가 아니라 객체로 바로 적용됩니다(색·fontFamily·fontSize·radius·pillRadius·gap·avatarShape). 다크 모드는theme="dark" | "auto".- 슬롯 함수는 DOM 이 아니라 React 노드를 반환합니다.
null/undefined반환 시 기본 렌더.data-partCSS 훅은 RN 에 없습니다(스타일은 슬롯으로).
import { QuipierComments } from "@quipier/react-native";
import { View, Text, Image } from "react-native";
<QuipierComments
apiKey="qp_…"
projectId="…"
pageId="/posts/1"
theme="auto"
appearance={{ accent: "#4f46e5", like: "#e11d48", avatarShape: "rounded", radius: 12 }}
features={{ sort: false, badge: false }}
slots={{
// 이미지 아바타 (부분 슬롯 — 나머지 row 는 기본)
avatar: (view) => (
<Image
source={{ uri: `https://cdn.example.com/u/${view.author.id}.png` }}
style={{ width: 32, height: 32, borderRadius: 8 }}
/>
),
// row 전체 교체 — 동작은 ctx.actions
comment: (view, ctx) => (
<View style={{ padding: 12, borderRadius: 12, borderWidth: 1, borderColor: "#e5e7eb" }}>
<Text style={{ fontWeight: "700" }}>{view.author.nickname}</Text>
<Text style={{ marginTop: 4 }}>{view.content}</Text>
<Text onPress={() => ctx.actions.like()} style={{ marginTop: 8, color: "#4f46e5" }}>
♥ {view.likes.count}
</Text>
</View>
),
}}
/>ctx 는 웹과 동일하게 defaultNode()(기본 노드를 그대로 반환 — 감싸기 가능) · actions · helpers · theme 를 제공합니다. view/슬롯 종류/features 키도 웹과 같습니다.