사용자가 하나 이상의 옵션을 선택할 수 있게 해주는 컴포넌트입니다. 목록에서 여러 항목을 선택하거나 약관 동의와 같은 선택적 작업에 사용됩니다.
@seed-design/lynx-react@0.1.0, @seed-design/lynx-css@0.1.0
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup
label = "관심 분야"
description = "관심 있는 분야를 모두 선택해 주세요."
indicator = "선택"
>
< Checkbox label = "디자인" tone = "neutral" size = "large" />
< Checkbox label = "개발" tone = "neutral" size = "large" defaultChecked />
< Checkbox label = "마케팅" tone = "neutral" size = "large" />
</ CheckboxGroup >
</ VStack >
</ view >
);
}
npx @seed-design/cli@latest add ui:checkbox pnpm dlx @seed-design/cli@latest add ui:checkbox yarn dlx @seed-design/cli@latest add ui:checkbox bun x @seed-design/cli@latest add ui:checkbox
의존성 설치
npm install @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react yarn add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react pnpm add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react bun add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react 아래 코드를 복사 후 붙여넣고 사용하세요 /**
* @file ui:checkbox
* @requires @seed-design/lynx-react@>=0.4.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.8.0 <1.0.0
**/
import * as React from "@lynx-js/react" ;
import IconCheckmarkFatFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFatFill" ;
import IconMinusFatFill from "@karrotmarket/lynx-monochrome-icon/IconMinusFatFill" ;
import { Checkbox as SeedCheckbox, Field as SeedField } from "@seed-design/lynx-react" ;
type FieldRootRef = React . ComponentRef < typeof SeedField.Root>;
export interface CheckboxProps extends SeedCheckbox . RootProps {
label ?: React . ReactNode ;
}
/**
* @see https://seed-design.io/lynx/components/checkbox
*/
export const Checkbox = React. forwardRef < unknown , CheckboxProps >(
({ label , children , "accessibility-label" : accessibilityLabel , ... otherProps }, ref ) => {
return (
< SeedCheckbox.Root
ref = {ref}
accessibility-label = {accessibilityLabel ?? ( typeof label === "string" ? label : undefined )}
{ ... otherProps}
>
< SeedCheckbox.Control >
< SeedCheckbox.Indicator
unchecked = {otherProps.variant === "ghost" ? < IconCheckmarkFatFill /> : undefined }
checked = {< IconCheckmarkFatFill />}
indeterminate = {< IconMinusFatFill />}
/>
</ SeedCheckbox.Control >
{label != null ? < SeedCheckbox.Label >{label}</ SeedCheckbox.Label > : null }
{children}
</ SeedCheckbox.Root >
);
},
);
Checkbox.displayName = "Checkbox" ;
export interface CheckmarkProps extends Omit < SeedCheckbox . RootProps , "children" > {}
/**
* @see https://seed-design.io/lynx/components/checkbox
*/
export const Checkmark = React. forwardRef < unknown , CheckmarkProps >(( props , ref ) => {
return (
< SeedCheckbox.Root ref = {ref} { ... props}>
< SeedCheckbox.Control >
< SeedCheckbox.Indicator
unchecked = {props.variant === "ghost" ? < IconCheckmarkFatFill /> : undefined }
checked = {< IconCheckmarkFatFill />}
indeterminate = {< IconMinusFatFill />}
/>
</ SeedCheckbox.Control >
</ SeedCheckbox.Root >
);
});
Checkmark.displayName = "Checkmark" ;
export interface CheckboxGroupProps
extends SeedCheckbox . GroupProps ,
Pick < SeedField . RootProps , "required" | "invalid" | "readOnly" > {
label ?: React . ReactNode ;
/**
* @default " medium "
*/
labelWeight ?: SeedField . LabelProps [ "weight" ];
indicator ?: React . ReactNode ;
showRequiredIndicator ?: boolean ;
description ?: React . ReactNode ;
errorMessage ?: React . ReactNode ;
}
/**
* @see https://seed-design.io/lynx/components/checkbox
*/
export const CheckboxGroup = React. forwardRef < FieldRootRef , CheckboxGroupProps >(
(
{
label,
labelWeight,
indicator,
showRequiredIndicator,
description,
errorMessage,
children,
... fieldProps
},
ref,
) => {
const renderHeader = label != null || indicator != null ;
const renderErrorMessage = errorMessage != null ;
const renderDescription = description != null && ! renderErrorMessage;
const renderFooter = renderDescription || renderErrorMessage;
return (
< SeedField.Root ref = {ref} { ... fieldProps}>
{renderHeader ? (
< SeedField.Header >
< SeedField.Label weight = {labelWeight}>
{label}
{showRequiredIndicator ? < SeedField.RequiredIndicator /> : null }
{indicator != null ? (
< SeedField.IndicatorText >{indicator}</ SeedField.IndicatorText >
) : null }
</ SeedField.Label >
</ SeedField.Header >
) : null }
< SeedCheckbox.Group >{children}</ SeedCheckbox.Group >
{renderFooter ? (
< SeedField.Footer >
{renderDescription ? (
< SeedField.Description >{description}</ SeedField.Description >
) : null }
{renderErrorMessage ? (
< SeedField.ErrorMessage >{errorMessage}</ SeedField.ErrorMessage >
) : null }
</ SeedField.Footer >
) : null }
</ SeedField.Root >
);
},
);
CheckboxGroup.displayName = "CheckboxGroup" ;
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
label?React.ReactNode
indicator?React.ReactNode
showRequiredIndicator?boolean | undefined
description?React.ReactNode
errorMessage?React.ReactNode
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
required?boolean | undefined
readOnly?boolean | undefined
label?React.ReactNode
checked?boolean | undefined
defaultChecked?boolean | undefined
indeterminate?boolean | undefined
onCheckedChange?(( checked : boolean ) => void ) | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
style?CSSProperties | undefined
className?string | undefined
checked?boolean | undefined
indeterminate?boolean | undefined
defaultChecked?boolean | undefined
onCheckedChange?(( checked : boolean ) => void ) | undefined
Checkbox.Root는 기본적으로 접근성 요소로 노출됩니다. checkbox 역할과 현재 선택 상태, 비활성 상태도 전달합니다. Registry Checkbox에 문자열 label을 전달하면 화면의 문구를 accessibility-label에도 연결합니다. 문자열이 아닌 label이나 children으로 문구를 구성할 때는 같은 내용을 accessibility-label에 직접 전달하세요.
import "./styles" ;
import { HStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< HStack className = "checkbox-preview" gap = "x8" >
< CheckboxGroup >
< Checkbox label = "Medium (default)" size = "medium" defaultChecked tone = "neutral" />
< Checkbox label = "Large" size = "large" defaultChecked tone = "neutral" />
</ CheckboxGroup >
< CheckboxGroup >
< Checkbox
label = "Medium (default)"
size = "medium"
variant = "ghost"
defaultChecked
tone = "neutral"
/>
< Checkbox label = "Large" size = "large" variant = "ghost" defaultChecked tone = "neutral" />
</ CheckboxGroup >
</ HStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup >
< Checkbox
label = "Square (default)"
variant = "square"
tone = "brand"
size = "large"
defaultChecked
/>
< Checkbox label = "Ghost" variant = "ghost" tone = "brand" size = "large" defaultChecked />
</ CheckboxGroup >
</ VStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup >
< Checkbox
label = "Square (default)"
variant = "square"
tone = "neutral"
size = "large"
defaultChecked
/>
< Checkbox label = "Ghost" variant = "ghost" tone = "neutral" size = "large" defaultChecked />
</ CheckboxGroup >
</ VStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< Checkbox defaultChecked label = "indeterminate" indeterminate tone = "neutral" size = "large" />
</ VStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup >
< Checkbox label = "Regular Label Text" weight = "regular" tone = "neutral" size = "large" />
< Checkbox label = "Bold Label Text" weight = "bold" tone = "neutral" size = "large" />
</ CheckboxGroup >
</ VStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup >
< Checkbox
size = "medium"
tone = "neutral"
label = "Consequat ut veniam aliqua deserunt occaecat enim occaecat veniam et et cillum nulla officia incididunt incididunt. Sint laboris labore occaecat fugiat culpa voluptate ullamco in elit dolore exercitation nulla."
/>
< Checkbox
size = "large"
tone = "neutral"
label = "Consequat ut veniam aliqua deserunt occaecat enim occaecat veniam et et cillum nulla officia incididunt incididunt. Sint laboris labore occaecat fugiat culpa voluptate ullamco in elit dolore exercitation nulla."
/>
</ CheckboxGroup >
</ VStack >
</ view >
);
}
import "./styles" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< CheckboxGroup >
< Checkbox
defaultChecked
label = "Disabled Checked, Square"
disabled
tone = "neutral"
size = "large"
/>
< Checkbox
checked = { false }
label = "Disabled without Checked, Square"
disabled
tone = "neutral"
size = "large"
/>
< Checkbox
variant = "ghost"
defaultChecked
label = "Disabled Checked, Ghost"
disabled
tone = "neutral"
size = "large"
/>
< Checkbox
variant = "ghost"
checked = { false }
label = "Disabled without Checked, Ghost"
disabled
tone = "neutral"
size = "large"
/>
</ CheckboxGroup >
</ VStack >
</ view >
);
}
onCheckedChange로 체크박스의 선택 상태 변경을 감지할 수 있습니다.
import "./styles" ;
import { useState } from "@lynx-js/react" ;
import { VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
const [ count , setCount ] = useState ( 0 );
const [ lastValue , setLastValue ] = useState < boolean | null >( null );
function handleCheckedChange ( checked : boolean ) {
"background only" ;
setCount (( previous ) => previous + 1 );
setLastValue (checked);
}
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" gap = "x4" >
< Checkbox
label = "Click me"
tone = "neutral"
size = "large"
onCheckedChange = {handleCheckedChange}
/>
< text className = "checkbox-preview__status" >
onCheckedChange called: {count} times, last value:{ " " }
{lastValue === null ? "-" : JSON . stringify (lastValue)}
</ text >
</ VStack >
</ view >
);
}
Lynx Registry의 Checkmark는 자체 Checkbox.Root를 포함합니다. 단독으로 사용할 때는 accessibility-label을 전달하세요. 문구까지 같은 탭 영역에 넣으려면 Registry Checkbox의 children을 사용합니다. 이때 accessibility-label도 직접 전달하세요.
import "./styles" ;
import { HStack, Text, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox } from "@/components/ui/checkbox" ;
interface CustomCheckboxProps {
label : string ;
textStyle : "t7Regular" | "t7Medium" | "t7Bold" ;
defaultChecked ?: boolean ;
}
function CustomCheckbox ({ label , textStyle , defaultChecked } : CustomCheckboxProps ) {
return (
< Checkbox
accessibility-label = {label}
tone = "neutral"
defaultChecked = {defaultChecked}
style = {{ flexDirection: "column" , rowGap: "8px" , alignItems: "center" }}
>
< Text textStyle = {textStyle}>{label}</ Text >
</ Checkbox >
);
}
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< HStack className = "checkbox-preview" gap = "x6" >
< CustomCheckbox label = "regular" textStyle = "t7Regular" />
< CustomCheckbox label = "medium" textStyle = "t7Medium" defaultChecked />
< CustomCheckbox label = "bold" textStyle = "t7Bold" />
</ HStack >
</ view >
);
}
CheckboxGroup을 사용하여 여러 체크박스를 그룹화하고 label, description, errorMessage 등의 Fieldset 관련 prop을 사용할 수 있습니다.
import "./styles" ;
import { useState } from "@lynx-js/react" ;
import { ActionButton, HStack, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
const [ apple , setApple ] = useState ( true );
const [ banana , setBanana ] = useState ( false );
const [ orange , setOrange ] = useState ( false );
const [ firstErrorMessage , setFirstErrorMessage ] = useState < string | undefined >();
const [ terms , setTerms ] = useState ( false );
const [ privacy , setPrivacy ] = useState ( true );
const [ marketing , setMarketing ] = useState ( false );
const [ secondErrorMessage , setSecondErrorMessage ] = useState < string | undefined >();
const handleFirstSubmit = () => {
setFirstErrorMessage (apple ? "Apple은 선택할 수 없습니다." : undefined );
};
const handleSecondSubmit = () => {
setSecondErrorMessage ( ! terms || ! privacy ? "필수 항목에 동의해 주세요." : undefined );
};
return (
< view className = { `${ seedClassName } docs-lynx-checkbox-root` }>
< VStack className = "checkbox-preview" >
< HStack width = "full" gap = "x8" align = "flex-start" >
< VStack style = {{ flex: 1 }} gap = "spacingY.componentDefault" >
< CheckboxGroup
label = "좋아하는 과일"
indicator = "선택"
description = "Apple을 선택하고 제출해보세요."
errorMessage = {firstErrorMessage}
>
< Checkbox
label = "Apple"
tone = "neutral"
size = "large"
checked = {apple}
onCheckedChange = {setApple}
/>
< Checkbox
label = "Banana"
tone = "neutral"
size = "large"
checked = {banana}
onCheckedChange = {setBanana}
/>
< Checkbox
label = "Orange"
tone = "neutral"
size = "large"
checked = {orange}
onCheckedChange = {setOrange}
/>
</ CheckboxGroup >
< ActionButton variant = "neutralSolid" bindtap = {handleFirstSubmit}>
제출
</ ActionButton >
</ VStack >
< VStack style = {{ flex: 1 }} gap = "spacingY.componentDefault" >
< CheckboxGroup
label = "약관 동의"
labelWeight = "bold"
showRequiredIndicator
description = "이용약관을 선택하지 않고 제출해보세요."
errorMessage = {secondErrorMessage}
>
< Checkbox
label = "이용약관 동의 (필수)"
tone = "neutral"
size = "large"
checked = {terms}
onCheckedChange = {setTerms}
/>
< Checkbox
label = "개인정보 처리방침 동의 (필수)"
tone = "neutral"
size = "large"
checked = {privacy}
onCheckedChange = {setPrivacy}
/>
< Checkbox
label = "마케팅 수신 동의 (선택)"
tone = "neutral"
size = "large"
checked = {marketing}
onCheckedChange = {setMarketing}
/>
</ CheckboxGroup >
< ActionButton variant = "neutralSolid" bindtap = {handleSecondSubmit}>
제출
</ ActionButton >
</ VStack >
</ HStack >
</ VStack >
</ view >
);
}
Lynx Checkbox는 React Checkbox와 다음과 같은 차이가 있습니다.
아이콘 주입 방식 : snippet의 Checkbox와 Checkmark는 @karrotmarket/lynx-monochrome-icon의 check / minus icon을 자동으로 주입합니다. compound component를 직접 조합할 때는 Checkbox.Indicator에 Lynx monochrome icon을 전달해야 합니다. raw <svg> 주입은 Lynx 범위 밖입니다.
오류 안내 렌더링 : React는 오류가 표시될 때 description을 접근성 트리에 남기지만, Lynx에는 DOM의 visually hidden 처리 방식이 없어 화면과 접근성 트리에서 description을 제외하고 errorMessage만 렌더링합니다.
이벤트 핸들링 : onChange 대신 onCheckedChange만 노출합니다. tap 핸들러는 Root 내부에서 소유합니다.
Pressed 상태 : 웹의 data-active 대신 내부 press state를 checkmark recipe의 pressed boolean variant로 전달합니다.
Scale Feedback : Lynx는 눌림 상태를 색상으로 표현하며 React의 --seed-checkmark-feedback-scale CSS 변수는 제공하지 않습니다.
접근성 속성 : HTML <label> / <input> 대신 네이티브 <view> / <text> 요소를 렌더링합니다. Checkbox.Root는 accessibility-element, accessibility-role-description, accessibility-value로 역할과 선택 상태를 전달합니다.
Compound 구조 : Checkbox.Root → Checkbox.Control → Checkbox.Indicator + Checkbox.Label 구성과 Context로 상태와 variant를 공유하는 흐름은 동일합니다.
ref forwarding : Checkbox, Checkmark, CheckboxGroup은 Root view로 ref를 전달합니다. 웹 snippet처럼 hidden input ref를 제공하지 않습니다.
현재 Lynx 플랫폼 제약으로 다음 기능이 지원되지 않습니다.
기능 웹 대응 설명 CheckboxHiddenInput<input type="checkbox">Lynx에 HTML form 제출 모델 없음 inputPropshidden input props Lynx snippet은 hidden input을 렌더링하지 않음 개별 Checkbox의 name / value / required / invalid form field props Lynx에 native form 제출 모델 없음 React Hook Form 예제 HTML form과 hidden input 연동 앱 상태와 checked / onCheckedChange를 직접 연결해야 함 focus / focusVisible키보드 포커스 Lynx에 키보드 포커스 개념 없음 onChange (raw DOM event)React.ChangeEvent의미 없음. onCheckedChange로 대체 weight="default" / "stronger"deprecated 호환 매핑 Lynx는 "regular" / "bold"만 노출