Skip to content

Composition

A guide to composing Base UI Vue components with your own Vue components.

Composing custom Vue components

Use the as prop, Slot, or useRender to compose a Base UI Vue part with your own components.

For example, Button renders a <button> by default. The code snippet below shows how to use a custom component instead.

vue
<script setup lang="ts">
import { Button } from 'base-ui-vue'
import MyButton from './MyButton.vue'
</script>

<template>
  <Button :as="MyButton">
    Save
  </Button>
</template>

The custom component should forward fallthrough attributes and refs to the underlying DOM element or component part that needs them.

Composing multiple components

In situations where you need to compose multiple Base UI Vue components together, the same pattern can be nested as deeply as necessary.

vue
<script setup lang="ts">
import {
  Button,
  FieldControl,
  FieldDescription,
  FieldLabel,
  FieldRoot,
  FieldsetLegend,
  FieldsetRoot,
  Form,
  mergeProps,
  Slot,
} from 'base-ui-vue'
import MyButton from './MyButton.vue'
import MyFieldContainer from './MyFieldContainer.vue'
import MyFormSection from './MyFormSection.vue'
import MyLegend from './MyLegend.vue'
import MyTextarea from './MyTextarea.vue'
</script>

<template>
  <Form :as="MyFormSection">
    <FieldsetRoot :as="MyFormSection">
      <FieldsetLegend :as="MyLegend">
        Profile
      </FieldsetLegend>

      <FieldRoot :as="MyFieldContainer" name="bio">
        <FieldLabel>Bio</FieldLabel>
        <FieldControl :as="MyTextarea" />
        <FieldDescription>
          Visible on your public profile.
        </FieldDescription>
      </FieldRoot>

      <Button v-slot="{ props, ref }" :as="Slot">
        <MyButton
          :ref="ref"
          v-bind="mergeProps(props, {
            type: 'submit',
            class: 'PrimaryButton',
          })"
        >
          Save profile
        </MyButton>
      </Button>
    </FieldsetRoot>
  </Form>
</template>

This is where useRender and mergeProps become important: as lets you swap component parts, Slot lets you take over the rendered markup, and mergeProps keeps the combined props and listeners intact.

Changing the default rendered element

Many parts can render a different HTML element or Vue component through the as prop.

For example, FieldControl renders an <input> by default, but can render another compatible control element when needed.

vue
<script setup lang="ts">
import { FieldControl, FieldRoot } from 'base-ui-vue'
</script>

<template>
  <FieldRoot>
    <FieldControl as="textarea" />
  </FieldRoot>
</template>

Each Base UI Vue component renders the most appropriate element by default, and in most cases rendering a different element is best kept to specific integration needs.

Renderless composition

When you need full control over the rendered markup, pass Slot to as and use the scoped slot payload to bind Base UI Vue props yourself.

vue
<script setup lang="ts">
import { Button, mergeProps, Slot } from 'base-ui-vue'
</script>

<template>
  <Button v-slot="{ props, ref }" :as="Slot">
    <span :ref="ref" v-bind="mergeProps(props, { class: 'ButtonText' })">
      Save
    </span>
  </Button>
</template>

This pattern gives you complete control over the rendered structure while keeping Base UI Vue's internal props, listeners, refs, and state attributes intact.