From 04cc7d287a5503544fbf5565e3d9e834ca19e88c Mon Sep 17 00:00:00 2001 From: LadyBluenotes Date: Tue, 16 Dec 2025 11:42:56 -0800 Subject: [PATCH 1/2] update polymorph page --- .../docs/core/overview/polymorphism.mdx | 145 +++++++++++------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx index 7e7c7e46..5f96c05b 100644 --- a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx +++ b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx @@ -1,10 +1,17 @@ +import { Callout } from "../../../../components/callout.tsx"; + # Polymorphism -All component parts that render a DOM element have an `as` prop. +Kobalte components that render a DOM element support polymorphism via the `as` prop. This allows you to change the rendered element or component while preserving behavior, accessibility, and state management. + +Polymorphism is useful when you want to: +- Change the underlying element (`button` → `a`) +- Integrate with your own design system components +- Control which props reach your component -## The `as` prop +## Basic usage -For simple use cases the `as` prop can be used, either with native HTML elements or custom Solid components: +Use `as` with a native element or a custom Solid component. ```tsx {8, 13} import { Tabs } from "@kobalte/core/tabs"; @@ -30,17 +37,20 @@ function App() { } ``` -## The `as` prop callback +Kobalte consumes its own options internally and forwards valid HTML attributes to the rendered element. + +## Using `as` callbacks -For more advanced use cases the `as` prop can accept a callback. -The main reason to use a callback over the normal `as` prop is being able to set props without interfering with Kobalte. +For full control over which props are passed, `as` can also be a callback. -When using this pattern the following rules apply to the callback: +When using an `as` callback: -- You must spread the props forwarded to your callback onto your node/component. -- Custom props are passed as is from the parent. -- Kobalte options are not passed to the callback, only the resulting html attributes. -- You should set your event handlers on the parent and not inside your callback. +- Always spread the provided props +- Custom props are forwarded **unchanged** +- Kobalte options are *not* passed +- Event handlers must be defined on the **parent** + +Violating these rules can break behavior or accessibility. ```tsx {17} import { Tabs } from "@kobalte/core/tabs"; @@ -50,16 +60,13 @@ function App() { return ( - {/* The `value` prop is used by Kobalte and not passed to MyCustomButton */} A Trigger - {/* The `value` prop is used by Kobalte and not passed to MyCustomButton */} ( - // The `value` prop is directly passed to MyCustomButton )} > @@ -71,8 +78,14 @@ function App() { ); } ``` +In this example: +- `value="one"` is used by Kobalte and not passed to `MyCustomButton` +- `value="custom"` is explicitly passed to `MyCustomButton` + + +### Typing `as` callbacks -You can optionally use a type helper to get the exact types passed to your callback: +You can use `PolymorphicCallbackProps` to get exact typing for callback props: ```tsx {6-9} import { Tabs, TabsTriggerOptions, TabsTriggerRenderProps } from "@kobalte/core/tabs"; @@ -97,15 +110,15 @@ import { PolymorphicCallbackProps } from "@kobalte/core/polymorphic"; ## Event lifecycle -Setting custom event handlers on component will call your custom handler before Kobalte's. +Custom event handlers defined on a Kobalte component are called *before* Kobalte’s internal handlers. -## Types +## Component Prop Types -This section is mainly for library authors that want to build on top of Kobalte and expose the correct types -to your end users. - -Every component that renders an HTML element has the following types: + + This section is intended for **library authors** building on top of Kobalte. + +Every Kobalte component that renders an element exposes four core types: - `ComponentOptions` - `ComponentCommonProps` - `ComponentRenderProps` @@ -120,93 +133,113 @@ This type allows components to accept Kobalte's props and all other props accept ### `ComponentOptions` -This type contains all custom props consumed by Kobalte, these props do not exist in HTML. -These are not passed to the HTML element nor to the `as` callback. +Custom props consumed internally by Kobalte. + +- Not valid HTML +- Not forwarded to the DOM +- Not passed to `as` callbacks ### `ComponentCommonProps` -This type contains HTML attributes optionally accepted by the Kobalte component and will -be forwarded to the rendered DOM node. These are managed by Kobalte but can be customized by the end -user. It includes attributes such as `id`, `ref`, event handlers, etc. The generic is used by `ref` and event handlers, -by default it is `HTMLElement`. +Optional HTML attributes that wil be accepted and forwarded to the rendered DOM node. + +- Includes `id`, `ref`, and event handlers +- Managed by Kobalte but customizable by the end user +- Generic is used by `ref` and event handlers, by default it is `HTMLElement`. ### `ComponentRenderProps` -This type extends `ComponentCommonProps` and additionally contains attributes that are passed -to the DOM node and fully managed by Kobalte. You should never assign these yourself or set them on -the Kobalte component. Modifying these props will break your component's behavior and accessibility. +Extends `ComponentCommonProps` with attributes that are passed to the DOM node and fully controlled by Kobalte. + +- These props must not be modified. +- Changing them will break behavior and accessibility. ### `ComponentProps` -This is the final type exported by components, it is equal to `ComponentOptions & Partial`. -It combines all props expected by Kobalte's component. The generic is used by the CommonProps, by default it is `HTMLElement`. +Public props type exported by the component. + +- Combines all props exepcted by Kobalte's component. +- Generic is used by `CommonProps`, by default is `HTMLElement`. + +Equivalent to `ComponentOptions` & `Partial`. ### `PolymorphicProps` -If you're writing a custom component and want to expose Kobalte's `as` prop to the end user -and keep proper typing, be sure to use `PolymorphicProps` for your props type. +Use `PolymorphicProps` when you’re building a wrapper component and want to expose Kobalte’s `as` prop to end users with correct typing. + +#### Example ```tsx import { Tabs, TabsTriggerProps } from "@kobalte/core/tabs"; import { PolymorphicProps } from "@kobalte/core/polymorphic"; +import type { ValidComponent } from "@kobalte/utils"; +import { splitProps } from "solid-js"; -// Optionally extend `TabsTriggerProps` if you wish to -// expose Kobalte props to your end user. interface CustomProps extends TabsTriggerProps { variant: "default" | "outline"; } -// Your generic `T` should extend ValidComponent and have a default value of the default DOM node. function CustomTabsTrigger( props: PolymorphicProps>, ) { - // Typescript degrades typechecking when using generics, as long as we - // spread `others` to our element, we can effectively ignore them. const [local, others] = splitProps(props as CustomProps, ["variant"]); return ( ); } ``` + + When using generics, TypeScript can lose some precision; splitting local props and spreading the remaining props helps preserve usability. + + + +Note: +- `T` should extend `ValidComponent` and default to the underlying element you render (eg. `"button"`). + +### Fixed element wrappers -If you do not wish to allow changing the element type, you can simplify your types by making -props: `OverrideComponentProps<"button", CustomProps>`, replace `"button"` with the correct -tagname for other components, imported from `"@kobalte/utils"`. +If your wrapper should always render a specific element type, and you don’t want to support `as`, you can simplify typing with `OverrideComponentProps` from `@kobalte/utils`: -If you also want to export exact types, you can re-export and extends component types: +For example, use `OverrideComponentProps<"button", CustomProps>` where `"button"` is the tag name you want to render. + +### Exporting exact types + +If you want to expose a strongly-typed surface area (eg. for downstream libraries), you can re-export and extend the underlying component types: ```tsx +import type { ValidComponent } from "@kobalte/utils"; +import type { + TabsTriggerOptions, + TabsTriggerCommonProps, + TabsTriggerRenderProps, +} from "@kobalte/core/tabs"; +import type { ElementOf, PolymorphicProps } from "@kobalte/core/polymorphic"; + export interface CustomTabsTriggerOptions extends TabsTriggerOptions { variant: "default" | "outline"; } -export interface CustomTabsTriggerCommonProps extends TabsTriggerCommonProps { - // If you allow users to set classes and extend them. - //class: string; -} +export interface CustomTabsTriggerCommonProps + extends TabsTriggerCommonProps {} export interface CustomTabsTriggerRenderProps extends CustomTabsTriggerCommonProps, TabsTriggerRenderProps { - // If you do not allow users to set classes and manage all of them. class: string; } -export type CustomTabsTriggerProps = CustomTabsTriggerOptions & - Partial>>; +export type CustomTabsTriggerProps = + CustomTabsTriggerOptions & Partial>>; export function CustomTabsTrigger( - props: PolymorphicProps, + props: PolymorphicProps>, ) {} + ``` -`ElementOf` is a helper from `"@kobalte/core/polymorphic"` that converts a tag name into its element -(e.g. `ElementOf<"button"> = HTMLButtonElement`). +`ElementOf` (from `@kobalte/core/polymorphic`) maps a tag name to its corresponding DOM element type (eg. `ElementOf<"button">` → `HTMLButtonElement`). From 8a3ad4554d415adf65b16903c41aad9fe9bb7a7d Mon Sep 17 00:00:00 2001 From: Sarah Date: Tue, 16 Dec 2025 11:58:00 -0800 Subject: [PATCH 2/2] Fix typo in polymorphism documentation --- apps/docs/src/routes/docs/core/overview/polymorphism.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx index 5f96c05b..aabcb2ea 100644 --- a/apps/docs/src/routes/docs/core/overview/polymorphism.mdx +++ b/apps/docs/src/routes/docs/core/overview/polymorphism.mdx @@ -158,7 +158,7 @@ Extends `ComponentCommonProps` with attributes that are passed to the DOM node a Public props type exported by the component. -- Combines all props exepcted by Kobalte's component. +- Combines all props expected by Kobalte's component. - Generic is used by `CommonProps`, by default is `HTMLElement`. Equivalent to `ComponentOptions` & `Partial`.