diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index c84e76c1b4..5e01aa412e 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -6,6 +6,10 @@ description: All notable changes will be documented in this file.
## [Unreleased]
+## Added
+
+- Added `factory` component for `asChild` prop.
+
## [0.1.0] - 2024-11-27
### Added
diff --git a/packages/svelte/src/lib/components/avatar/avatar-fallback.svelte b/packages/svelte/src/lib/components/avatar/avatar-fallback.svelte
index e9f76c2e71..5a564ef623 100644
--- a/packages/svelte/src/lib/components/avatar/avatar-fallback.svelte
+++ b/packages/svelte/src/lib/components/avatar/avatar-fallback.svelte
@@ -1,13 +1,14 @@
-
- {@render props.children?.()}
-
+
diff --git a/packages/svelte/src/lib/components/avatar/avatar-image.svelte b/packages/svelte/src/lib/components/avatar/avatar-image.svelte
index 91a499aa16..b7c1966552 100644
--- a/packages/svelte/src/lib/components/avatar/avatar-image.svelte
+++ b/packages/svelte/src/lib/components/avatar/avatar-image.svelte
@@ -1,17 +1,18 @@
-
+
diff --git a/packages/svelte/src/lib/components/avatar/avatar-root-provider.svelte b/packages/svelte/src/lib/components/avatar/avatar-root-provider.svelte
index 5227e042ba..f7d6ea171d 100644
--- a/packages/svelte/src/lib/components/avatar/avatar-root-provider.svelte
+++ b/packages/svelte/src/lib/components/avatar/avatar-root-provider.svelte
@@ -15,6 +15,7 @@
import { createSplitProps } from '$lib/utils/create-split-props'
import { mergeProps } from '@zag-js/svelte'
import { AvatarProvider } from './use-avatar-context'
+ import { Ark } from '../factory'
const props: AvatarRootProviderProps = $props()
const [{ value: avatar }, localProps] = createSplitProps()(props, ['value'])
@@ -24,6 +25,4 @@
const mergedProps = $derived(mergeProps(avatar().getRootProps(), localProps))
-
- {@render props.children?.()}
-
+
diff --git a/packages/svelte/src/lib/components/avatar/avatar-root.svelte b/packages/svelte/src/lib/components/avatar/avatar-root.svelte
index fe2a33b01a..5d00365ba4 100644
--- a/packages/svelte/src/lib/components/avatar/avatar-root.svelte
+++ b/packages/svelte/src/lib/components/avatar/avatar-root.svelte
@@ -1,9 +1,9 @@
-
- {@render props.children?.()}
-
+
diff --git a/packages/svelte/src/lib/components/factory/examples/basic.svelte b/packages/svelte/src/lib/components/factory/examples/basic.svelte
new file mode 100644
index 0000000000..aae4166beb
--- /dev/null
+++ b/packages/svelte/src/lib/components/factory/examples/basic.svelte
@@ -0,0 +1,31 @@
+
+
+
+ {#snippet render(props)}
+
+ Ark UI
+
+ {/snippet}
+
diff --git a/packages/svelte/src/lib/components/factory/factory.svelte b/packages/svelte/src/lib/components/factory/factory.svelte
new file mode 100644
index 0000000000..b4f2907cb4
--- /dev/null
+++ b/packages/svelte/src/lib/components/factory/factory.svelte
@@ -0,0 +1,22 @@
+
+
+{#if asChild}
+ {@render render?.(propsFn)}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/packages/svelte/src/lib/components/factory/factory.test.ts b/packages/svelte/src/lib/components/factory/factory.test.ts
new file mode 100644
index 0000000000..cb49531281
--- /dev/null
+++ b/packages/svelte/src/lib/components/factory/factory.test.ts
@@ -0,0 +1,40 @@
+import { render, screen } from '@testing-library/svelte'
+import user from '@testing-library/user-event'
+import { describe, expect, it, vi } from 'vitest'
+import ComponentUnderTest from './examples/basic.svelte'
+
+describe('Ark Factory', () => {
+ it('should render only the child', () => {
+ render(ComponentUnderTest)
+
+ expect(() => screen.getByTestId('parent')).toThrow()
+ expect(screen.getByTestId('child')).toBeVisible()
+ })
+
+ it('should override existing props', () => {
+ render(ComponentUnderTest)
+ const child = screen.getByTestId('child')
+ expect(child.id).toBe('child')
+ // biome-ignore lint/complexity/useLiteralKeys:
+ expect(child.dataset['part']).toBe('child')
+ })
+
+ it('should merge styles and classes', () => {
+ render(ComponentUnderTest)
+ const child = screen.getByTestId('child')
+ expect(child).toHaveStyle({ background: 'red' })
+ expect(child).toHaveStyle({ color: 'rgb(0, 0, 255)' })
+ expect(child).toHaveClass('child parent')
+ expect(screen.getByText('Ark UI')).toBeVisible()
+ })
+
+ it('should merge events', async () => {
+ const onClickParent = vi.fn()
+ const onClickChild = vi.fn()
+ render(ComponentUnderTest, { onClickParent, onClickChild })
+ await user.click(screen.getByTestId('child'))
+
+ expect(onClickParent).toHaveBeenCalled()
+ expect(onClickChild).toHaveBeenCalled()
+ })
+})
diff --git a/packages/svelte/src/lib/components/factory/index.ts b/packages/svelte/src/lib/components/factory/index.ts
new file mode 100644
index 0000000000..47d8a9c1c5
--- /dev/null
+++ b/packages/svelte/src/lib/components/factory/index.ts
@@ -0,0 +1 @@
+export { default as Ark } from './factory.svelte'
diff --git a/packages/svelte/src/lib/components/index.ts b/packages/svelte/src/lib/components/index.ts
index 886c6ec3a0..1f7ac46059 100644
--- a/packages/svelte/src/lib/components/index.ts
+++ b/packages/svelte/src/lib/components/index.ts
@@ -1 +1,2 @@
export * from './avatar'
+export * from './factory'
diff --git a/packages/svelte/src/lib/types.ts b/packages/svelte/src/lib/types.ts
index bf034f9fe9..ea0cb0d974 100644
--- a/packages/svelte/src/lib/types.ts
+++ b/packages/svelte/src/lib/types.ts
@@ -1,9 +1,19 @@
-import type { SvelteHTMLElements } from 'svelte/elements'
+import type { Snippet } from 'svelte'
+import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements'
export type Assign = Omit & U
export type Optional = Pick, K> & Omit
export type CollectionItem = string | object
-export type HTMLProps = SvelteHTMLElements[T]
export type Accessor = () => T
+
+export type HTMLTag = keyof SvelteHTMLElements
+export type PropsFn = (props?: HTMLProps) => HTMLAttributes
+
+export type HTMLProps = SvelteHTMLElements[T]
+export type PolymorphicProps = {
+ asChild?: boolean
+ children?: Snippet
+ render?: Snippet<[PropsFn]>
+}