Behavioral Defaults
@vuecs/core ships a parallel resolution system for non-class behavioral props — button text, placeholders, content strings, visibility toggles. This is the i18n hook: configure default text once at app.use(vuecs, ...), optionally with reactive sources, and every component picks it up.
Why a second system?
The theme system handles CSS classes. But things like <VCFormSubmit>'s "Create" / "Update" labels, <VCFormSelect>'s placeholder option text, and <VCListItem>'s textPropName aren't classes — they're plain values. Putting them through the theme resolver would conflate two different concerns. So they get their own manager with the same shape.
Resolution layers
For each resolved key, three layers in priority order:
1. Instance prop (non-undefined) ← highest priority
2. Global defaults (from app.use() config) ← may be MaybeRef (ref / computed)
3. Hardcoded fallback (passed to composable) ← lowest priorityImportant contract
Two rules govern what shows up on defaults.value:
- The hardcoded object drives the shape. The composable iterates
Object.keys(hardcoded). If you set a global default for a key not listed in hardcoded, it's silently dropped. If you add a new configurable key, list it in hardcoded too. - Only
undefinedtriggers fallthrough.nullon the instance prop wins over both global and hardcoded. This lets consumers deliberately "unset" — but meansnullcannot be used to toggle a boolean off (passfalseinstead).
Setup API
import vuecs from '@vuecs/core';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
app.use(vuecs, {
themes: [tailwindTheme()],
defaults: {
formSubmit: {
createText: computed(() => t('actions.create')),
updateText: computed(() => t('actions.update')),
},
listNoMore: {
content: computed(() => t('list.noMore')),
},
formSelect: {
optionDefaultValue: computed(() => t('forms.selectPlaceholder')),
},
},
});computed values unwrap transparently — when the locale changes, the resolved value updates and dependent components re-render.
Consumer side
<!-- Uses the global default 'actions.create' -->
<VCFormSubmit />
<!-- Override per instance -->
<VCFormSubmit createText="Save changes" />
<!-- Explicitly unset (falls through to hardcoded fallback 'Create') -->
<VCFormSubmit :create-text="null" />Composite components — important
When a composite component (e.g. VCList) forwards behavioral props to a child component whose prop is resolved via useComponentDefaults, the composite's own Vue prop.default must be undefined. Otherwise, the composite always wins layer 1 on the child and shadows the child's global defaults entirely.
See VCList.noMoreContent and VCList.itemTextPropName (both default: undefined) for the canonical pattern.
Migrated components
| Component | Configurable keys |
|---|---|
VCFormSubmit | type, icon, createText, updateText |
VCFormSelect | optionDefault, optionDefaultId, optionDefaultValue |
VCFormGroup | validation |
VCFormInputCheckbox | labelContent |
VCListItem | textPropName |
VCListNoMore | content |
Each component's Vue prop.default is undefined; the effective default lives in the behavioralDefaults constant inside the component.
Type-safe keys
The augmentable ComponentDefaults interface registers each component's keys:
declare module '@vuecs/core' {
interface ComponentDefaults {
formSubmit?: ComponentDefaultValues<{
createText: string;
updateText: string;
icon: boolean;
}>;
}
}ComponentDefaultValues<T> wraps each field as MaybeRef<T[K] | undefined> so reactive and plain values are both accepted.
Resolver API
DefaultsManager— holds thePartial<ComponentDefaults>map in ashallowRef;setDefaults()for runtime updates.installDefaultsManager(app, options)— Vue plugin; called from@vuecs/core's top-levelinstall()and from each component package.useComponentDefaults(name, props, hardcoded)— composable; returnsComputedRef<T>that recomputes on prop or manager state change.
See also
- Theme System — the parallel system for class strings
- Installation — where to wire
defaultsintoapp.use()