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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
ryo
May 22, 2026
230
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Polymorphic Components パターンで作る、型安全でセマンティックな UI コンポーネント
ryo
May 22, 2026
Featured
See All Featured
Tell your own story through comics
letsgokoyo
1
980
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
240
A better future with KSS
kneath
240
18k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
640
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
620
How to make the Groovebox
asonas
2
2.2k
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Utilizing Notion as your number one productivity tool
mfonobong
4
330
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
950
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
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 採用