src/Umbraco.Web.UI.Client/docs/style-guide.md
← Umbraco Backoffice | ← Monorepo Root
Files:
my-component.element.tsmy-component.test.tsmy-component.stories.tsmy-component.controller.tsmy-component.context.tsmy-component.modal.tsmy-component.workspace.tsmy-component.repository.tsindex.ts (barrel exports)Classes & Types:
PascalCase with Umb prefix: UmbMyComponentPascalCase with Umb prefix: UmbMyInterfacePascalCase with Umb, Ufm, Manifest, Meta, or Example prefixExampleMyTypeVariables & Functions:
camelCase without underscore: myVariable, myMethodcamelCase with leading underscore: _myPrivateVariablecamelCase without underscore: #myPrivateFieldcamelCase with optional underscore: myProtected or _myProtectedUPPER_SNAKE_CASE with UMB_ prefix: UMB_MY_CONSTANTUPPER_CASE or camelCaseCustom Elements:
umb- prefix: umb-my-componentHTMLElementTagNameMapindex.ts) for package public APIs{
"printWidth": 120,
"singleQuote": true,
"semi": true,
"bracketSpacing": true,
"bracketSameLine": true,
"useTabs": true
}
Strict Mode (enabled in tsconfig.json):
strict: truenoImplicitReturns: truenoFallthroughCasesInSwitch: truenoImplicitOverride: trueType Features:
type for unions/intersections: type MyType = A | Binterface for object shapes and extension: interface MyInterface extends Baseconst over let, never use varreadonly for immutable propertiesany (lint warning), use unknown insteadas const for literal typesModule Syntax:
import/exportimport type { MyType } from '...'export type { MyType }Decorators:
@customElement('umb-my-element') - Register custom element@property({ type: String }) - Reactive properties@state() - Internal reactive state@query('#myId') - Query shadow DOMobj?.property?.method?.()value ?? defaultValue`Hello ${name}`const { a, b } = obj{ ...obj, newProp: value }const fn = () => {}map, filter, reduce, find, some, everyObject.keys, Object.values, Object.entries#privateFieldEvent handlers must be arrow function properties to prevent memory leaks:
// ✅ GOOD: Arrow function property
export class UmbMyElement extends LitElement {
#onStorageEvent = async (evt: StorageEvent) => {
// Handler logic
};
constructor() {
super();
window.addEventListener('storage', this.#onStorageEvent);
}
disconnectedCallback() {
window.removeEventListener('storage', this.#onStorageEvent);
}
}
// ❌ BAD: Using .bind(this) creates new function references
export class UmbBadElement extends LitElement {
constructor() {
super();
// Each .bind(this) creates a NEW reference!
window.addEventListener('storage', this.#onStorageEvent.bind(this));
}
disconnectedCallback() {
// This creates ANOTHER reference - doesn't remove the original!
window.removeEventListener('storage', this.#onStorageEvent.bind(this));
}
#onStorageEvent(evt: StorageEvent) {
// Handler logic
}
}
Placement in Class:
Naming:
#onEventName or #handleEventName_onEventName or _handleEventName#onStorageEvent, #handleDragEnter, #onActionExecutedvar (use const/let)eval() or Function() constructorwith statementarguments object (use rest parameters)value!value as Type@ts-ignore (use @ts-expect-error with comment)Project-Specific Rules:
prefer-static-styles-last - Static styles property must be last in classenforce-umbraco-external-imports - External dependencies must be imported via @umbraco-cms/backoffice/external/*var keywordAllowed JSDoc Tags (for web-component-analyzer):
@element - Element name@attr - HTML attribute@fires - Custom events@prop - Properties@slot - Slots@cssprop - CSS custom properties@csspart - CSS parts@description, @param, @returns, @example// TODO: description [initials]@deprecated tag with migration instructions