| name | style-set-create |
| description | 새 컴포넌트의 StyleSet을 v2.0.0+ 컨벤션에 맞게 생성 |
Style-Set 생성 가이드
새로운 컴포넌트의 StyleSet을 v2.0.0+ 컨벤션 (extends CSSProperties + $ prefix) 으로 설계하고 구현합니다.
사용 시기
- 새 컴포넌트를 만들 때
- StyleSet 설계 방향이 불확실할 때
- 처음부터 컨벤션을 지키고 싶을 때
Style-Set 컨벤션
VsXxxStyleSet은 CSSProperties를 상속하며, 키는 $ prefix 유무로 구분됩니다.
| 키 형태 | 의미 | 처리 |
|---|
width, padding, ... (non-$) | 표준 CSS — 컴포넌트 루트에 inline style로 적용 | componentInlineStyle로 자동 수집 |
$X (string/number) | CSS 변수로 노출 | --vs-{kebab-component}-X 로 emit (styleSetVariables) |
$X (object) | 슬롯/요소 스타일 또는 하위 StyleSet | componentStyleSet.$X로 직접 접근 |
import type { CSSProperties } from 'vue';
export interface VsButtonStyleSet extends CSSProperties {
$padding?: string;
$content?: CSSProperties;
$loading?: VsLoadingStyleSet;
}
💡 root는 extends CSSProperties로 이미 노출되므로 component?: CSSProperties 같은 래퍼 키는 만들지 않습니다.
생성 프로세스
1단계: 요구사항 분석
컴포넌트 분석
- 이름:
Vs[ComponentName]
- 기능: 무엇을 하는가?
- 시각적 구조: 어떤 주요 요소들로 구성되는가?
- 하위 컴포넌트: 다른 Vlossom 컴포넌트를 사용하는가?
스타일 분석
- 자주 변경되는 스타일: 사용자가 자주 조정할 속성은?
- 고정 스타일: 변하지 않아야 할 디자인 요소는?
- 상태별 스타일: hover, focus, disabled, active, selected 등?
- 테마 변형: primary, outline → ColorScheme에서 처리
2단계: 키 분류 결정 트리
이 속성을 외부에서 자주 커스터마이징하나?
├── No → CSS에 하드코딩 (디자인 토큰 우선: var(--vs-comp-bg) 등)
└── Yes → 어떻게 노출할지 결정
├── 임의 CSS로 자유롭게 변경되어도 OK → root CSSProperties (extends 덕분에 자동)
├── .css에서 calc/의사 요소/상태 셀렉터에 쓰임 → $X primitive
├── 특정 슬롯/요소 스타일링 → $X: CSSProperties
└── 하위 Vlossom 컴포넌트 전체 스타일 전파 → $X: VsXxxStyleSet
$X primitive (CSS 변수) 사용 기준 — 4가지 모두 Yes일 때만
위 조건에 해당하지 않으면 root CSSProperties로 충분합니다. width, height, padding, margin, boxShadow, backgroundColor 등은 일반적으로 root에서 받는 것이 자연스럽습니다.
ColorScheme이 담당하는 영역은 노출하지 말 것
--vs-comp-bg, --vs-comp-font 등 기본 테마 색상은 ColorScheme이 자동 처리합니다. $backgroundColor, $fontColor로 별도 노출하지 마세요. 사용자가 일회성으로 바꿔야 한다면 root CSSProperties (backgroundColor) 로 직접 줄 수 있습니다.
3단계: 타입 정의 작성
import type { ComponentPublicInstance, CSSProperties } from 'vue';
import type Vs[ComponentName] from './Vs[ComponentName].vue';
import type { VsChildStyleSet } from '@/components/vs-child/types';
declare module 'vue' {
interface GlobalComponents {
Vs[ComponentName]: typeof Vs[ComponentName];
}
}
export type { Vs[ComponentName] };
export interface Vs[ComponentName]Ref extends ComponentPublicInstance<typeof Vs[ComponentName]> {}
export interface Vs[ComponentName]StyleSet extends CSSProperties {
$arrowSize?: string;
$element?: CSSProperties;
$childComponent?: VsChildStyleSet;
}
extends CSSProperties로 root CSS가 자동 노출되므로 component?: CSSProperties 같은 래퍼 키는 만들지 않습니다.
4단계: 컴포넌트 구현
Setup
import { computed, defineComponent, ref, toRefs, type ComputedRef, type Ref } from 'vue';
import { VsComponent } from '@/declaration';
import { useColorScheme, useStyleSet } from '@/composables';
import { getColorSchemeProps, getStyleSetProps } from '@/props';
import { objectUtil } from '@/utils';
import type { Vs[ComponentName]StyleSet } from './types';
const componentName = VsComponent.Vs[ComponentName];
export default defineComponent({
name: componentName,
props: {
...getColorSchemeProps(),
...getStyleSetProps<Vs[ComponentName]StyleSet>(),
},
setup(props) {
const { colorScheme, styleSet } = toRefs(props);
const { colorSchemeClass } = useColorScheme(componentName, colorScheme);
const baseStyleSet: Ref<Vs[ComponentName]StyleSet> = ref({
$childComponent: {
width: '100%',
},
});
const additionalStyleSet = computed<Vs[ComponentName]StyleSet>(() => {
return objectUtil.shake({
height: props.height || undefined,
});
});
const { componentStyleSet, styleSetVariables, componentInlineStyle } =
useStyleSet<Vs[ComponentName]StyleSet>(
componentName,
styleSet,
baseStyleSet,
additionalStyleSet,
);
return {
colorSchemeClass,
styleSetVariables,
componentInlineStyle,
componentStyleSet,
};
},
});
useStyleSet 인자 규칙
| 인자 | 용도 | 반응성 |
|---|
baseStyleSet (3번째) | 하위 컴포넌트 기본값, 고정 스타일 | ref({}) 권장 (고정값), computed 허용 (반응성 필요시) |
additionalStyleSet (4번째) | props에서 오는 동적 값 (사용자 styleSet보다 우선) | computed (항상) |
⚠️ additionalStyleSet은 사용자 styleSet을 덮어쓰므로 신중히 사용하세요. props가 styleSet을 침범하지 않는 게 일반적입니다.
Template
<template>
<div
:class="['vs-[component-kebab]', colorSchemeClass, classObj]"
:style="{ ...styleSetVariables, ...componentInlineStyle }"
>
<!-- 슬롯/요소 -->
<div
class="vs-[component-kebab]-element"
:style="componentStyleSet.$element"
>
<slot name="element" />
</div>
<!-- 하위 컴포넌트 -->
<vs-child-component :style-set="componentStyleSet.$childComponent" />
<slot />
</div>
</template>
핵심 포인트:
- 루트 요소:
{ ...styleSetVariables, ...componentInlineStyle } 두 가지를 spread
- 슬롯/요소:
componentStyleSet.$X (CSSProperties) → :style
- 하위 컴포넌트:
componentStyleSet.$X (StyleSet) → :style-set
상태 스타일 적용 (nested-state)
$step?: CSSProperties & { $active?: CSSProperties };
computed로 base + 상태 스타일을 머지:
const stepStyle = computed<CSSProperties>(() => ({
...componentStyleSet.value.$step,
...(isActive.value ? componentStyleSet.value.$step?.$active : {}),
}));
5단계: CSS 작성
.vs-[component-kebab] {
--vs-[component-kebab]-arrowSize: initial;
display: flex;
flex-direction: column;
padding-right: var(--vs-[component-kebab]-arrowSize, 1rem);
background-color: var(--vs-comp-bg);
color: var(--vs-comp-font);
border-radius: calc(var(--vs-radius-ratio) * var(--vs-radius-md));
}
.vs-[component-kebab]-element {
display: flex;
align-items: center;
gap: 0.5rem;
}
.vs-[component-kebab]:hover {
opacity: 0.9;
}
.vs-[component-kebab].vs-disabled {
opacity: 0.5;
cursor: not-allowed;
}
규칙 요약:
- 변수명:
--vs-{component-kebab}-{property} (property는 camelCase 유지)
- 인터페이스의 모든
$X primitive ↔ CSS 변수가 1:1로 매칭되고 실제 사용되어야 함
- 사용하지 않을 변수는 만들지 말 것
- 단위는 rem 우선 (px 지양)
- 기본 색상은 디자인 토큰 (
var(--vs-comp-bg) 등) 직접 사용
6단계: 검증 체크리스트
타입 정의
컴포넌트
CSS
문서
실전 예제
요청: "VsCard 컴포넌트 StyleSet 생성"
1단계: 분석
- 이름: VsCard, 콘텐츠를 카드로 표시
- 구조: header / body / footer
- 하위 컴포넌트: 없음
- 자주 변경: width, padding, boxShadow → root CSSProperties로 충분
- 고정: border-radius 비율, 기본 배경/글자색 → CSS 하드코딩 (디자인 토큰)
- 변형: primary, outlined → ColorScheme
2단계: 키 분류
width, padding, boxShadow, backgroundColor → root CSSProperties (extends로 자동)
- header/body/footer →
$header, $body, $footer (object)
$X primitive로 노출할 만한 속성 없음 (.css에서 calc/의사 요소가 없음)
3단계: 타입
import type { ComponentPublicInstance, CSSProperties } from 'vue';
import type VsCard from './VsCard.vue';
declare module 'vue' {
interface GlobalComponents {
VsCard: typeof VsCard;
}
}
export type { VsCard };
export interface VsCardRef extends ComponentPublicInstance<typeof VsCard> {}
export interface VsCardStyleSet extends CSSProperties {
$header?: CSSProperties;
$body?: CSSProperties;
$footer?: CSSProperties;
}
사용 예시
<vs-card
:style-set="{
width: '320px',
padding: '1rem',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
$header: { fontWeight: 600 },
$footer: { borderTop: '1px solid #eee' },
}"
/>
주의사항
🚫 하지 말아야 할 것
component?: CSSProperties 같은 래퍼 키 추가 — extends CSSProperties로 이미 노출됨
- 옛
variables?: { ... } 구조 사용 — v2.0.0+ 컨벤션 아님
- 모든 속성을
$X (CSS 변수)로 — .css에서 실제 변수로 쓰이는 것만 노출
- 3단계 이상 중첩 —
$X: { $Y: { ... } }는 자식 StyleSet이 자체적으로 깊이를 가질 때만 허용
- 의미 없는 그룹명 —
$styles, $config, $inner 등 모호한 이름
- ColorScheme 영역 침범 —
$backgroundColor, $fontColor 등
- 동작 기반 네이밍 —
$expand 대신 $content처럼 내용 기반으로
✅ 해야 할 것
- 사용자 관점에서 생각 — 어떤 속성을 자주 조정할까?
- 명확한 네이밍 — 변수명만 봐도 용도를 알 수 있게
- 적절한 fallback — CSS 변수는 항상 fallback 제공
- JSDoc 문서화 — 각 속성의 용도 설명
도움말: 어디에 두지?
root CSSProperties (extends로 자동):
- width, height, padding, margin, boxShadow, backgroundColor 등
- 임의 CSS로 자유롭게 변경되어도 무방한 속성
$X primitive (CSS 변수):
.css에서 calc()나 의사 요소(::after)에서 참조해야 함
- 특정 요소 크기 (arrow-size 등)
- 상태 셀렉터에서 동적으로 값이 바뀌어야 함
$X object (CSSProperties):
$X (자식 StyleSet):
- 하위 Vlossom 컴포넌트의 스타일을 일괄 제어 (예:
$loading, $chip, $wrapper)
참고 문서
사용 방법
/style-set-create VsCard
대화형으로 요구사항을 파악하고 단계별로 생성합니다.