Skip to content

Global configuration ​

CDS 4 lets you set default prop values — globally, for a specific component, or only inside a given subtree. You describe "what buttons look like in our product" once and stop repeating the same props in every template.

Setup ​

Default values are set through the defaults option of createCds. The global key affects every component; a key named after a component affects only that one.

ts
import { createApp } from 'vue';
import '@central-design-system/components/styles';
import { createCds, components, directives } from '@central-design-system/components';

import App from './App.vue';

const cds = createCds({
  components,
  directives,
  defaults: {
    global: {
      size: 'sm'
    },
    CdsButton: {
      appearance: 'secondary'
    },
    CdsTextInput: {
      hideDetails: 'auto'
    }
  }
});

createApp(App).use(cds);

Important. Props in defaults are written in camelCase: hideDetails, not hide-details.

A component key is its name from defineOptions({ name }) — CdsButton, CdsTextInput, CdsCardActions and so on. Subcomponent names (CdsCardTitle, CdsListItem) work exactly like root component names.

Resolution order ​

For every prop, CDS takes the first value that applies:

  1. the prop explicitly passed in the template;
  2. defaults.<ComponentName>.<prop> from the nearest defaults context;
  3. defaults.global.<prop>;
  4. the default value declared in the component itself.

An explicitly passed prop always wins — defaults can never override the template.

Changing defaults at runtime ​

createCds returns an instance whose defaults live in a reactive ref. You can change them on the fly — every mounted component recalculates:

ts
const cds = createCds({ components, directives });

// for example, turn on compact mode based on a user setting
cds.defaults.value = {
  ...cds.defaults.value,
  global: { size: 'sm' }
};

Contextual defaults ​

Defaults can be set for components nested inside another component. For example, to make every button inside a card secondary by default:

ts
createCds({
  defaults: {
    CdsCard: {
      CdsButton: { appearance: 'secondary' }
    }
  }
});

The rule is simple: a capitalized key is the name of a nested component, a lowercase key is a prop of the current component. Nesting is not limited to one level:

ts
createCds({
  defaults: {
    CdsCard: {
      CdsCardActions: {
        CdsButton: { appearance: 'tertiary', size: 'sm' }
      }
    }
  }
});

The library uses the same mechanism internally so that grouping components can hand their settings down to their children:

  • CdsButtonGroup sets the appearance, color, baseColor, size props on nested CdsButtons;
  • CdsTagGroup sets the color, activeColor, disabled, filter props on CdsTag;
  • CdsSelectionControlGroup passes settings to the controls, with the target component name set by the defaultsTarget prop (CdsCheckbox, CdsRadio and so on);
  • CdsTabs, CdsSteps, CdsList, CdsBreadcrumbs, CdsButtonToggle, CdsExpandablePanels, CdsOtpInput work the same way.

That's why <CdsButtonGroup size="sm"> makes every button inside small — you don't have to set size on each of them.

Global class and style ​

Besides props, a component's defaults can carry class and style. It's a convenient way to apply product-specific rules to every instance of a component without touching the templates:

ts
createCds({
  defaults: {
    CdsButton: {
      class: 'my-button'
    }
  }
});

Any value Vue accepts works — a string, an object, an array:

ts
createCds({
  defaults: {
    CdsButton: {
      style: [{ textTransform: 'none' }]
    }
  }
});

Unlike ordinary props, class and style are not overwritten by the template but merged with it. So <CdsButton class="mt-2"> gets both my-button and mt-2.

Important. class and style only work under a specific component's key. In global they are ignored.

Aliases ​

An alias is the same component registered under a different name with its own defaults. Aliases are a handy way to assemble "product" variants of components.

ts
import { createCds, components, directives, CdsButton } from '@central-design-system/components';

const cds = createCds({
  components,
  directives,
  aliases: {
    CdsButtonPrimary: CdsButton,
    CdsButtonDanger: CdsButton
  },
  defaults: {
    CdsButtonPrimary: {
      appearance: 'primary',
      class: 'my-button--primary'
    },
    CdsButtonDanger: {
      appearance: 'primary',
      color: 'red',
      class: 'my-button--danger'
    }
  }
});

Aliases are registered globally, so you can use them in templates right away:

vue
<template>
  <CdsButtonPrimary>Save</CdsButtonPrimary>
  <CdsButtonDanger class="ml-2">Delete</CdsButtonDanger>
</template>

Important. An alias does not inherit the base component's defaults. If the configuration has defaults.CdsButton, it will not apply to <CdsButtonPrimary> — defaults are looked up by the alias name. Move shared settings into global or duplicate them under the alias key.

Defaults for part of the application ​

To set defaults for a subtree rather than globally, use the CdsDefaultsProvider component. The Controls panel in Storybook is built on it too: the values from the panel are applied to the example's components as defaults.

vue
<script setup lang="ts">
import { CdsButton, CdsDefaultsProvider } from '@central-design-system/components';
</script>

<template>
  <CdsDefaultsProvider :defaults="{ CdsButton: { appearance: 'tertiary', size: 'sm' } }">
    <CdsButton>Small tertiary button</CdsButton>
  </CdsDefaultsProvider>
</template>
PropTypePurpose
defaultsDefaultsOptionsDefaults for the subtree; merged with the inherited ones by default
scopedbooleanDon't mix with the parent defaults — only the ones set here apply
resetnumber | stringGo N levels up the defaults chain
rootboolean | stringFall back to the root defaults from createCds; with a string — to a named group inside them
disabledbooleanProvide nothing — the subtree gets the parent defaults as they are

The typical use for scoped is an "island" inside someone else's environment: enclosing groups (CdsButtonGroup, say) should not impose their settings on your block.

Your own components ​

Your application's own components can take part in the defaults system too. Pass their props through useDefaults:

vue
<script setup lang="ts">
import { useDefaults } from '@central-design-system/components';

const _props = defineProps<{ foo?: string }>();
const props = useDefaults(_props, 'MyComponent');
</script>

<template>
  <div>I am {{ props.foo }}</div>
</template>

useDefaults takes two arguments, both optional:

  • props — the props object returned by defineProps;
  • name — the component name used to look up the key in defaults. If omitted, the name from defineOptions({ name }) is used, and failing that, the file name.

Now the component is configurable from the shared configuration:

ts
createCds({
  defaults: {
    MyComponent: { foo: 'bar' }
  }
});

Important. In the template, refer to props.foo, not foo. useDefaults returns a proxy over the props, and destructured defineProps variables bypass it:

diff
-<div>I am {{ foo }}</div>
+<div>I am {{ props.foo }}</div>

Nested defaults in your own components ​

To make your component hand defaults down to its children (the way CdsButtonGroup does), just call useDefaults() with no arguments — the nested keys from its defaults section go into the child context:

vue
<script setup lang="ts">
import { useDefaults } from '@central-design-system/components';

useDefaults();
</script>

<template>
  <div class="my-panel">
    <slot />
  </div>
</template>
ts
createCds({
  defaults: {
    MyPanel: {
      CdsButton: { appearance: 'tertiary' }
    }
  }
});

If the children's defaults depend on the component's props, use provideDefaults directly:

vue
<script setup lang="ts">
import { provideDefaults } from '@central-design-system/components';
import { toRef } from 'vue';

const props = defineProps<{ size?: 'sm' | 'md' }>();

provideDefaults({
  CdsButton: {
    size: toRef(() => props.size)
  }
});
</script>

<template>
  <div class="my-toolbar">
    <slot />
  </div>
</template>

Blueprint — a configuration preset ​

If several applications need the same set of options, extract it into a blueprint: a plain createCds options object that the rest of the configuration is layered on top of.

ts
import { createCds, components, directives } from '@central-design-system/components';
import type { Blueprint } from '@central-design-system/components';

const productBlueprint: Blueprint = {
  theme: { defaultTheme: 'b2bLight' },
  defaults: {
    CdsButton: { appearance: 'secondary' }
  }
};

const cds = createCds({
  blueprint: productBlueprint,
  components,
  directives,
  // overrides the value from the blueprint
  defaults: {
    CdsButton: { size: 'sm' }
  }
});

Options are merged deeply (mergeDeep), so in the example above the buttons keep appearance: 'secondary' from the preset and gain size: 'sm'.