Lightning Web Components
A reusable LWC is a product—not a code snippet
Reuse begins when another team depends on you
A developer extracts a record panel into a shared Lightning Web Component, adds two @api properties and announces that it is reusable. Three teams adopt it. One needs a compact layout, another dispatches its event across several containers, and a third places it in Flow. Six months later, a harmless rename breaks pages the owning team did not know existed.
The architectural change happened at the first external consumer. The component stopped being an implementation detail and became a product with an interface, users, compatibility obligations and an operating cost. Reuse can reduce duplicated code, but only when the shared contract is smaller and more stable than the implementations it replaces.
Start with a job, not a universal component
Do not begin with an ambition such as ‘one card for every object.’ Begin with a coherent user job: select a related record, summarise an approval decision or collect a validated date range. A component is reusable when several contexts share that behaviour and meaning—not merely when its colours, labels and fields can be parameterised.
Every option expands the state space that maintainers must understand and test. If consumers need flags such as isFlow, isConsole, showFooter, useAlternativeHeader and legacyMode, the abstraction is probably joining unrelated experiences. Keep a stable behavioural core and let small composition wrappers adapt it to a target. Some duplication at the edge is cheaper than a central component nobody can safely change.
Write the public contract before the JavaScript
List the public properties, methods, events, slots and builder configuration a consumer may rely on. For each input, define its meaning, type, default, valid values and behaviour when absent or invalid. For each output, define when it occurs, whether it can occur more than once and which detail fields are stable. Document loading, empty, error, disabled and read-only states as part of the contract.
Salesforce uses @api to expose properties and methods, while the component's .js-meta.xml file controls targets and builder-facing configuration. Those are technical exposure mechanisms, not a complete design. A public Boolean called compact is ambiguous; a documented density value with defined layouts is a contract. Prefer semantic inputs over switches that reveal internal markup or one consumer's temporary requirement.
Keep data ownership outside the reusable view
A shared component that queries records, applies business policy, performs mutations and renders the result is difficult to reuse because every consumer inherits its data assumptions and security context. Decide whether the component is a presentational view, a business capability or an orchestration container. Mixing all three makes change impact hard to predict.
For a broadly reusable view, accept a small data shape and emit intent such as select, change or retry. Let an owning container obtain data through Lightning Data Service, GraphQL, Apex or another appropriate mechanism, then translate it into the view contract. A domain component may legitimately own data access, but its name, permissions, caching and failure behaviour must then be domain-specific rather than presented as a generic widget.
Events are public APIs
Salesforce documents custom events as the mechanism for communicating up the containment hierarchy. The event name, detail payload and propagation behaviour are therefore part of the component's interface. Renaming selected to recordselected, changing an identifier into a full record or dispatching before state settles can break consumers just as surely as changing an Apex method signature.
Keep event payloads minimal and copy values into detail rather than exposing mutable internal objects. Default to the narrowest propagation that serves the owner. Salesforce notes that bubbles and composed default to false and recommends avoiding broad composed events when they are unnecessary. An event that crosses a shadow boundary can become part of a much larger API surface, increasing collision and coupling risk.
Choose slots or data deliberately
Slots let a parent supply markup, which is excellent when the shared component owns structure while the consumer owns content. A data-driven interface is stronger when the child must interpret, validate, sort or consistently render a complex model. Salesforce's composition guidance recommends the data approach for complex use cases and illustrates it with components such as lightning-datatable.
The trade-off is control. Slots give consumers flexibility but allow inconsistent semantics, spacing and accessibility into the composition. Data gives the component a stronger guarantee but can create a large configuration language. Expose one model intentionally. Avoid accepting both arbitrary markup and a sprawling data schema for the same region unless the additional contract is justified by real consumers.
Targets multiply obligations
A component can be exposed through .js-meta.xml to Lightning App Builder, Experience Builder, Flow screens and other Salesforce targets. Each target adds a different configuration and runtime context. A component that behaves correctly on a record page may be confusing in Flow, constrained on mobile or unsafe for an unauthenticated Experience Cloud audience.
Expose only the targets that the owner actively supports. Maintain a target matrix covering context inputs, navigation, form factor, permissions, builder properties and failure behaviour. When Flow needs state validation or Experience Builder needs editable properties, design those contracts explicitly. Availability in a builder is a promise to administrators; it should not be enabled simply because the metadata tag is easy to add.
Compatibility needs a policy
Classify changes before release. Adding an optional property with a safe default may be compatible. Removing a property, changing a default, narrowing accepted values, altering an event payload or changing visible semantics is not. Styling can also be breaking when consumers built layouts around documented dimensions or states.
Prefer additive evolution: introduce the new input, support the old path, migrate known consumers and remove legacy behaviour only through an agreed deprecation window. Do not let version flags accumulate indefinitely. When two contracts genuinely need different behaviour, create a new component boundary or versioned facade and keep the stable core private. Source control records code history; it does not replace a consumer migration plan.
Test the contract from the consumer side
Salesforce provides sfdx-lwc-jest for unit testing Lightning Web Components. Use it to assert public behaviour: rendering for supported inputs, dispatched event names and detail, method outcomes, validation, keyboard interaction and loading, empty and error states. Avoid tests that mirror every private DOM detail; they make refactoring expensive without protecting the consumer contract.
Add representative integration tests for each supported target and a small visual and accessibility regression set. Test with realistic permissions, translated labels, long content, mobile widths and assistive technology—not only an administrator on a desktop. A base component can provide accessible primitives, but composition, focus order, error messaging and custom interaction remain the shared component owner's responsibility.
Give the component an owner and evidence
A reusable component needs a named owner who approves its contract, triages defects and coordinates breaking change. Maintain a short catalogue entry with purpose, supported targets, examples, accessibility notes, dependencies, consumers and release status. Capture usage through repository search, dependency information and adoption telemetry where available instead of relying on memory.
The JSBC Labs view
Treat shared LWCs as internal products. Start with a repeated user job, minimise the public surface, separate data ownership deliberately, constrain event propagation and support only the targets you can test. Publish examples and compatibility rules before asking other teams to adopt the component.
The goal is not maximum reuse. It is dependable reuse: consumers can adopt a component without learning its internals, owners can improve it without surprising consumers, and administrators can configure it without creating invalid states. @api makes a member public; disciplined product ownership makes that public interface worth depending on.
Official references
- Salesforce Developers: Use Components in Salesforce Targets
- Salesforce Developers: XML Configuration File Elements
- Salesforce Developers: Composition
- Salesforce Developers: Slots Versus Data
- Salesforce Developers: Create and Dispatch Events
- Salesforce Developers: Configure Event Propagation
- Salesforce Developers: End-to-End Tests for Lightning Web Components