Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Polymorphic Components パターンで作る、型安全でセマンティックな UI ...
Search
ryo
May 22, 2026
52
0
Share
Polymorphic Components パターンで作る、型安全でセマンティックな UI コンポーネント
ryo
May 22, 2026
Featured
See All Featured
Six Lessons from altMBA
skipperchong
29
4.2k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
61
44k
Mobile First: as difficult as doing things right
swwweet
225
10k
BBQ
matthewcrist
89
10k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.7k
Test your architecture with Archunit
thirion
1
2.2k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Automating Front-end Workflow
addyosmani
1370
200k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
300
How to make the Groovebox
asonas
2
2.2k
Transcript
Polymorphic Components パターン で作る、型安全でセマンティックな UI コンポーネント 2026.5.23 TSKaigi2026 ryo (
@ryohiy_ )
株式会社Belong FEE/SWE 最近は Three.js を好きで触っています ryo (@ryohiy_) 自己紹介
例えば、以下のような Text コンポーネントを実装 デザインシステム コンポーネント実装時の課題 export function Text({ children, ...props
}) { return <span {...props}>{children}</span> }
例えば、以下のような Text コンポーネントを実装 デザインシステム コンポーネント実装時の課題 export function Text({ children, ...props
}) { return <span {...props}>{children}</span> } Text を使うと常に span になる -> ❌ Semantic HTML 呼び出し側で要素を自由に宣言出来るようにしたい
デザインシステムのコンポーネント実装やUI ライブラリで利用 するデザインパターン コンポーネントの要素を呼び出し側から宣言する事を 出来るようにする Polymorphic Components とは
実装例 export function Text({ as, children, ...props }) { const
Component = as || 'span' return <Component {...props}>{children}</Component> } Text コンポーネントを Polymorphic にすると
実装例 export function Text({ as, children, ...props }) { const
Component = as || 'span' return <Component {...props}>{children}</Component> } Text コンポーネントを Polymorphic にすると as として HTML Tag の指定を受け取り、jsx の type として利用する ※ 型 assertion の 「as」とは異なるものです。これは Polymorphic Components の as prop パターン
実装例 <Text as="h1">ページタイトル</Text> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> 利用側: <h1>ページタイトル</h1> <time dateTime="2026-05-23">2026年5月23日</time>
レンダリング後:
実装例 利用側: レンダリング後: ❌ as= hoge ❌ as=h1 , dateTime=”2026-05-23”
<Text as="h1">ページタイトル</Text> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> <h1>ページタイトル</h1> <time dateTime="2026-05-23">2026年5月23日</time>
実装例 利用側: レンダリング後: ❌ as= hoge ❌ as=h1 , dateTime=”2026-05-23”
<Text as="h1">ページタイトル</Text> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> コードを書いている時に気づけるようにしたい => 型定義を活用する <h1>ページタイトル</h1> <time dateTime="2026-05-23">2026年5月23日</time>
実装① cva でデザイントークンを定義 const textVariants = cva('', { variants: {
size: { md: 'text-base', lg: 'text-3xl', }, weight: { normal: 'font-normal', bold: 'font-bold', }, } })
実装② 自前の props を定義 interface TextOwnProps<T extends ElementType = 'span'>
extends VariantProps<typeof textVariants> { as?: T }
実装② 自前の props を定義 interface TextOwnProps<T extends ElementType = 'span'>
extends VariantProps<typeof textVariants> { as?: T }
実装③ HTML 要素の props と合成 type TextProps<T extends ElementType =
'span'> = TextOwnProps<T> & Omit<ComponentProps<T>, keyof TextOwnProps<T> | 'className'> <T> の HTML 属性を取得
実装③ HTML 要素の props と合成 type TextProps<T extends ElementType =
'span'> = TextOwnProps<T> & Omit<ComponentProps<T>, keyof TextOwnProps<T> | 'className'> 自前 prop と衝突するキーを除外
実装④ コンポーネントを実装 export function Text<T extends ElementType = 'span'>({ as,
size, weight, children, ...props }: TextProps<T>) { const Component = as || 'span' return ( <Component className={textVariants({ size, weight })} {...props}> {children} </Component> )}
実装④ コンポーネントを実装 export function Text<T extends ElementType = 'span'>({ as,
size, weight, children, ...props }: TextProps<T>) { const Component = as || 'span' return ( <Component className={textVariants({ size, weight })} {...props}> {children} </Component> )} ②と同じ形
実装④ コンポーネントを実装 export function Text<T extends ElementType = 'span'>({ as,
size, weight, children, ...props }: TextProps<T>) { const Component = as || 'span' return ( <Component className={textVariants({ size, weight })} {...props}> {children} </Component> )} ③の実装
利用例 <Text as="h1" size="lg" weight="bold">ページタイトル</Text> // => <h1 class="text-3xl font-bold">ページタイトル</h1>
<Text as="p">本文テキスト</Text> // => <p class="text-base font-normal">本文テキスト</p> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> // => <time class="text-base font-normal" datetime="2026-05-23">2026年5月23日</time> OK
利用例 <Text as="h1" size="lg" weight="bold">ページタイトル</Text> // => <h1 class="text-3xl font-bold">ページタイトル</h1>
<Text as="p">本文テキスト</Text> // => <p class="text-base font-normal">本文テキスト</p> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> // => <time class="text-base font-normal" datetime="2026-05-23">2026年5月23日</time> OK
利用例 <Text as="h1" size="lg" weight="bold">ページタイトル</Text> // => <h1 class="text-3xl font-bold">ページタイトル</h1>
<Text as="p">本文テキスト</Text> // => <p class="text-base font-normal">本文テキスト</p> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> // => <time class="text-base font-normal" datetime="2026-05-23">2026年5月23日</time> OK
利用例 <Text as="h1" size="lg" weight="bold">ページタイトル</Text> // => <h1 class="text-3xl font-bold">ページタイトル</h1>
<Text as="p">本文テキスト</Text> // => <p class="text-base font-normal">本文テキスト</p> <Text as="time" dateTime="2026-05-23">2026年5月23日</Text> // => <time class="text-base font-normal" datetime="2026-05-23">2026年5月23日</time> OK
利用例 <Text as="hoge">ページタイトル</Text> // => ElementType で type check error
<Text as="p" size="sm">本文テキスト</Text> // => size は sm を持たないため type check error <Text as="h1" dateTime="2026-05-23">2026年5月23日</Text> // => h1 は dateTime 属性を持たないため type check error NG
利用例 <Text as="hoge">ページタイトル</Text> // => ElementType で type check error
<Text as="p" size="sm">本文テキスト</Text> // => size は sm を持たないため type check error <Text as="h1" dateTime="2026-05-23">2026年5月23日</Text> // => h1 は dateTime 属性を持たないため type check error NG
利用例 <Text as="hoge">ページタイトル</Text> // => ElementType で type check error
<Text as="p" size="sm">本文テキスト</Text> // => size は sm を持たないため type check error <Text as="h1" dateTime="2026-05-23">2026年5月23日</Text> // => h1 は dateTime 属性を持たないため type check error NG
利用例 <Text as="hoge">ページタイトル</Text> // => ElementType で type check error
<Text as="p" size="sm">本文テキスト</Text> // => size は sm を持たない為 type check error <Text as="h1" dateTime="2026-05-23">2026年5月23日</Text> // => h1 は dateTime 属性を持たないため type check error NG
参考資料 • https://github.com/shadcn-ui/ui • https://www.components.build/polymorphism • https://web.dev/learn/html/semantic-html • https://developer.mozilla.org/ja/docs/Glossary/Semantics •
https://developer.mozilla.org/ja/docs/Learn_web_development/Core/ Accessibility/HTML • https://zenn.dev/tsuboi/articles/8abddb1ae3038f
おわりに 本題のブログ Belong 採用