vuetifyjs / vuetifyjs/vuetify

RFC: Component-Scoped Defaults & Internal Roles

Open
#22,685 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

E: defaults T: feature
Dominant language
TypeScript
Stars
41k
Forks
7.1k
Avg merge
1d 21h
Merged PRs (30d)
11

Description

Problem Definition

Problem to solve

Vuetify provides a powerful defaults system, but several limitations appear when building complex design systems or reusable blueprints.

1. Limited targeting of internal components

Nested defaults currently affect all components of the same type within the scope.

Example:

defaults: {
  VCarousel: {
    VBtn: { color: 'primary' }
  }
}

This affects every VBtn inside the component, even if those buttons serve different roles.

There is currently no way to target specific internal elements.


2. Growing number of ad-hoc props

Many components include highly specific props to support small customizations.

Examples include props like:

menu-elevation (https://vuetifyjs.com/en/api/v-autocomplete/#props-menu-elevation)
icon-size (https://vuetifyjs.com/en/api/v-alert/#props-icon-size)
icon-color (https://vuetifyjs.com/en/api/v-field/#props-icon-color)

This leads to:

  • increasing API surface
  • inconsistent patterns across components
  • harder learning curve for new users

3. Awkward usage of VDefaultsProvider

Localized defaults currently require wrapping templates:

<VDefaultsProvider :defaults="{ VIcon: { color: 'success' } }">
  <v-btn prepend-icon="mdi-check-circle" />
</VDefaultsProvider>

This introduces extra wrappers that:

  • clutter templates
  • break the visual correspondence between DOM structure and UI structure

4. Slots required for small changes

Simple changes often require full slot overrides:

<v-btn prepend-icon="mdi-check-circle">
  <template #prepend>
    <v-icon color="success" />
  </template>
</v-btn>

This breaks encapsulation and duplicates internal logic.


Proposed solution

Summary

This RFC proposes extending Vuetify’s defaults system to allow component-scoped configuration of internal elements without requiring VDefaultsProvider, ad-hoc props, or full slot overrides.

The proposal introduces:

  1. Component-Scoped Defaults – allowing compound components to accept a defaults prop.
  2. Internal Roles – explicit namespaced roles representing internal parts of a component.

This approach would:

  • reduce the number of ad-hoc props
  • improve API consistency
  • simplify component customization
  • improve support for design systems and blueprints

Motivation

These limitations become particularly visible when building full design systems on top of Vuetify, especially when using the Blueprints feature.

When recreating complex design systems:

  • many customizations need to target specific internal parts
  • developers must combine slots, wrappers, and ad-hoc props
  • customization patterns become inconsistent

A structured system for internal customization would provide:

  • cleaner component APIs
  • predictable customization patterns
  • better support for design systems
  • simpler mental model for developers

Proposed Solution

This RFC proposes extending the defaults system with component-scoped defaults and internal roles.

1. Component-Scoped Defaults

Compound components would accept a new prop:

defaults

Example:

<v-btn
  :defaults="{
    VBtn: {
      PrependIcon: { color: 'success' },
      AppendIcon: { color: 'warning' }
    }
  }"
  prepend-icon="mdi-check-circle"
  append-icon="mdi-account-circle"
/>

This allows customizing internal elements without:

  • wrappers
  • slot overrides
  • extra props

2. Internal Roles

Compound components define explicit internal roles representing structural parts of the component.

Conceptual example:

roles: {
  PrependIcon: VIcon,
  AppendIcon: VIcon,
  Loader: VProgressCircular
}

Roles represent the component anatomy and enable granular targeting.


3. Namespaced Role Targeting

Roles are namespaced by component to avoid collisions.

Example:

VBtn.PrependIcon
VBtn.AppendIcon

Or nested syntax:

{
  VBtn: {
    PrependIcon: { color: 'success' }
  }
}

4. Cascading Defaults

Defaults resolve using a cascading model similar to CSS:

  1. Component instance defaults
  2. Parent defaults
  3. Global defaults
  4. Internal component defaults

The closest definition takes precedence.


5. Slot Compatibility

Slots receive resolved props so developers can decide whether to use the computed defaults.

<v-btn :defaults="{ VBtn: { PrependIcon: { color: 'success' }}}">
  <template #prepend="{ props }">
    <v-icon v-bind="props" />
  </template>
</v-btn>

Alternatives Considered

Maintaining the current system

The current approach relies on a combination of:

  • VDefaultsProvider
  • ad-hoc props
  • slot overrides

While functional, it leads to growing complexity and API inconsistency.


Adding more specialized props

Another approach would be introducing additional props such as:

menu-elevation
icon-color
menu-offset

However this further increases API surface and inconsistency.


Extending only VDefaultsProvider

Improving VDefaultsProvider alone would still require wrapper components and would not address the structural targeting problem.


Related discussion

A similar idea was discussed in:

https://github.com/vuetifyjs/vuetify/pull/22313#discussion_r2513392076

This RFC expands on that direction by introducing explicit internal roles.


Technical Design

Compound components define internal roles representing structural elements.

Conceptual example:

export const VBtnRoles = {
  PrependIcon: VIcon,
  AppendIcon: VIcon,
  Loader: VProgressCircular
}

Defaults are resolved lazily only when a role is rendered.

Pseudo implementation:

function resolveRoleDefaults(role) {
  return merge(
    internalDefaults[role],
    globalDefaults[role],
    parentDefaults[role],
    componentDefaults[role]
  )
}

Slots receive resolved props for full flexibility.


Adoption & Migration

This proposal follows a progressive adoption strategy.

Phase 1

Introduce:

  • defaults prop in compound components

No breaking changes.

Phase 2
  • Internal role definitions
  • Encourage the new pattern in documentation and examples.
Phase 3

Gradually deprecate some ad-hoc props that can be replaced by role defaults.


Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the existing defaults system and VDefaultsProvider implementation, then read the related discussion in pull request #22313. Compare the proposed component-scoped defaults, internal roles, cascading behavior, and slot compatibility with current behavior. Done means an agreed technical design with a scoped adoption plan and clear compatibility expectations.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
design, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.