Skip to main content
Custom Element Architecture

The Flumegro Lens: Evaluating Custom Element Reusability in a Multi-Framework World

When a team decides to build a component once and share it across React, Vue, Angular, and Svelte projects, custom elements seem like the obvious answer. The Web Component standard is framework-agnostic by design, and the promise of a single codebase that works everywhere is compelling. But anyone who has tried to ship a reusable custom element into a multi-framework environment knows that the gap between theory and practice is wide. The component might render, but does it feel native? Does it handle forms correctly? Can developers pass complex data without fighting the framework? This guide offers a structured lens—the Flumegro Lens—for evaluating custom element reusability before you invest months in a shared component library. We focus on qualitative benchmarks and practical trade-offs, not fabricated statistics. The goal is to help teams decide which approach fits their stack, team size, and performance constraints.

When a team decides to build a component once and share it across React, Vue, Angular, and Svelte projects, custom elements seem like the obvious answer. The Web Component standard is framework-agnostic by design, and the promise of a single codebase that works everywhere is compelling. But anyone who has tried to ship a reusable custom element into a multi-framework environment knows that the gap between theory and practice is wide. The component might render, but does it feel native? Does it handle forms correctly? Can developers pass complex data without fighting the framework? This guide offers a structured lens—the Flumegro Lens—for evaluating custom element reusability before you invest months in a shared component library.

We focus on qualitative benchmarks and practical trade-offs, not fabricated statistics. The goal is to help teams decide which approach fits their stack, team size, and performance constraints. We assume you are already familiar with the basics of custom elements and Web Components; here we go deeper into the decisions that determine whether your reusable components will be a productivity win or a maintenance burden.

Who Needs to Choose and Why Timing Matters

The decision to build reusable custom elements typically arises when an organization has two or more front-end frameworks in active use—perhaps a legacy Angular app being gradually migrated to React, or a product team that uses Vue for internal tools and React for customer-facing features. The cost of maintaining separate component libraries for each framework quickly becomes unsustainable. Custom elements offer a way to unify UI code without forcing a framework migration.

Timing is critical. If you start building reusable components too early, before your design system is stable, you will waste effort rewriting APIs and refactoring styles. If you wait too long, you accumulate technical debt as each team builds its own version of the same button, modal, or date picker. A good rule of thumb is to begin the evaluation when you have at least two framework teams requesting the same component for the third time. That pattern signals that the cost of duplication has crossed the threshold where a shared component would pay off.

Another timing factor is the maturity of the Web Component ecosystem. While the standard itself is stable, tooling for testing, bundling, and server-side rendering is still evolving. Teams should assess whether their target frameworks have mature support for custom elements. React, for example, has improved its handling of custom events and form participation, but still requires workarounds for passing complex props. Vue and Svelte handle custom elements more naturally, while Angular's support is solid but requires careful zone.js management.

The Flumegro Lens encourages teams to evaluate reusability not as a binary yes-or-no question, but as a spectrum. A component that works perfectly in a Vue app might break in React due to event handling differences. Before you commit to a cross-framework strategy, you need to define what “reusable” means in your context: does it mean the component renders identically, or does it also need to participate in form validation, accessibility tree, and framework reactivity?

Assessing Your Current Component Inventory

Start by auditing the components that are duplicated across your codebases. Count how many times each component type appears, and note the differences in implementation. If the same button component exists in three frameworks with slightly different APIs, that is a strong candidate for unification. If the components are fundamentally different in behavior—for example, a React modal that uses portals vs. a Vue modal that uses teleport—you may need to standardize the design first.

Cost of Delay vs. Cost of Premature Abstraction

Both extremes are expensive. Premature abstraction leads to over-engineered components that try to satisfy every possible future use case, resulting in bloated APIs and poor performance. Delaying too long means you spend months rewriting the same logic. Use a simple cost model: estimate the hours spent per duplicate component per quarter. When that number exceeds the estimated effort to build a shared version, it is time to act.

Three Approaches to Cross-Framework Reusability

Most teams consider one of three strategies when building reusable custom elements: vanilla custom elements with no framework wrappers, framework-specific wrapper components that encapsulate the custom element, or a hybrid approach using a middleware library like Lit or Stencil. Each has distinct trade-offs in developer experience, bundle size, and long-term maintainability.

Vanilla Custom Elements

Writing a custom element using only native APIs (customElements.define, shadow DOM, HTML templates) produces the smallest possible bundle with zero framework dependencies. This approach is ideal for components that need to be used in many different environments, including non-SPA pages or static sites. The downsides are significant: you must manually handle attribute reflection, property binding, and event dispatching in a way that feels natural to each framework. React developers, for example, will need to use refs and add event listeners manually, which reduces the developer experience.

Vanilla components also lack built-in reactivity. If your component needs to update efficiently when data changes, you must implement your own change detection or use a lightweight library. This can lead to boilerplate code that is hard to maintain across many components.

Framework-Specific Wrappers

In this approach, you build the core component as a custom element, then create a thin wrapper for each target framework. For example, a React wrapper would expose props as React-style attributes and handle events via callback props. This gives each team a native-feeling API while keeping the underlying implementation shared. The wrapper layer adds minimal bundle size (usually a few kilobytes per framework) and can be generated automatically with tools like @lit/react or stencil-ds-output-targets.

The main risk is that wrappers can become outdated as frameworks evolve. When React changes its event system or Vue alters its reactivity model, all wrappers need to be updated. Teams must invest in continuous integration that rebuilds and tests wrappers against each framework version.

Hybrid Middleware (Lit, Stencil, etc.)

Libraries like Lit and Stencil provide a developer-friendly way to build custom elements with reactive data binding, efficient rendering, and built-in support for attribute reflection. They compile to vanilla custom elements, so the output is framework-agnostic, but the development experience is closer to a modern component framework. This middle ground is popular because it reduces boilerplate while still producing standards-compliant components.

The trade-off is a dependency on the library itself. If Lit or Stencil stops being maintained, you may need to migrate your components. Also, the compiled output may be larger than hand-optimized vanilla code, though the difference is often negligible for all but the smallest components.

Criteria for Comparing Reusability Strategies

To evaluate which approach fits your team, we recommend a set of qualitative criteria. These are not hard metrics but lenses that reveal trade-offs in your specific context.

Developer Experience (DX) for Each Framework Team

How does the component feel to a developer who is used to the idioms of their framework? For a React developer, passing a function as a prop should feel natural. For a Vue developer, v-model should work out of the box. Vanilla custom elements often fail this test because they require manual wiring. Framework wrappers score high on DX because they hide the custom element internals. Hybrid middleware can achieve good DX if the library provides framework-specific bindings.

Bundle Size Impact

Measure the additional bytes each approach adds to the application bundle. Vanilla custom elements add the least overhead, often just the component code itself. Framework wrappers add a small amount for the wrapper logic. Hybrid middleware adds the library runtime (Lit is about 5 KB minified and gzipped; Stencil is similar). For a single component, the difference is small, but for a library of 50 components, the runtime overhead of a middleware library can add up.

Cross-Framework Compatibility

Test your component in each target framework with realistic usage: passing objects, arrays, functions; handling form submission; and interacting with the shadow DOM. Vanilla custom elements have the broadest compatibility because they rely only on browser APIs. Framework wrappers can introduce framework-specific bugs if the wrapper is not carefully maintained. Hybrid middleware components are generally compatible as long as the compiled output follows the spec.

Long-Term Maintainability

Consider who will maintain the components in two years. Vanilla components require deep Web Component knowledge, which may be rare on your team. Framework wrappers add a maintenance surface for each framework. Hybrid middleware centralizes the development in one codebase with a familiar syntax, which can be easier to hand off to new team members.

Testing and Debugging

Vanilla custom elements can be tested in any browser environment without framework overhead. Framework wrappers require testing in each framework's test runner, which multiplies the test suite. Hybrid middleware often provides testing utilities that work across frameworks, reducing duplication.

Trade-Offs at a Glance: A Structured Comparison

The following table summarizes the key trade-offs across the three approaches. Use it as a starting point for your own evaluation, but adjust the weights based on your team's priorities.

CriterionVanilla Custom ElementsFramework-Specific WrappersHybrid Middleware (Lit, Stencil)
Developer ExperienceLow (manual wiring)High (native feel per framework)Medium to High (depends on bindings)
Bundle SizeLowestLow (wrapper only)Medium (library runtime)
Cross-Framework CompatibilityHighestHigh (if wrappers are maintained)High (spec-compliant output)
Long-Term MaintainabilityLow (requires expert knowledge)Medium (multiple wrappers to update)High (single codebase, familiar syntax)
Testing EffortLow (browser tests only)High (per-framework tests)Medium (library utilities help)
Learning Curve for TeamSteep (Web Component internals)Moderate (wrapper pattern)Moderate (learn one library)

No single approach wins across all criteria. The right choice depends on your team's size, the number of target frameworks, and your tolerance for dependency risk. A small team with two frameworks may prefer vanilla custom elements to avoid any library dependency. A large organization with five frameworks and a dedicated platform team may invest in framework wrappers for the best developer experience. A team that values developer productivity and maintainability often leans toward hybrid middleware.

Composite Scenario: A Fintech Company with React and Angular

Consider a fintech company that has a React-based customer portal and an Angular-based internal admin tool. They need a shared date picker component that must handle localization, accessibility, and form validation. The team evaluates vanilla custom elements but finds that the React developers struggle with event handling and the Angular developers need to wrap the component in a directive to use it with reactive forms. They decide on hybrid middleware using Lit. The Lit component provides reactive properties and built-in form association, and the team uses @lit/react to generate a React wrapper. The Angular team uses the custom element directly with Angular's CUSTOM_ELEMENTS_SCHEMA. The result is a single codebase with good DX for both teams, at the cost of a 5 KB runtime.

Implementation Path After Choosing an Approach

Once you have selected a strategy, the implementation process follows a pattern that can be adapted to any approach. The key is to start small, validate early, and iterate.

Step 1: Define the Component API

Write a clear specification for each component: what properties it accepts (including types), what events it emits, and what slots it provides. This spec should be framework-agnostic. For example, a modal component might accept an `open` boolean property, emit a `close` event, and have a default slot for content. The spec should also define how the component participates in forms (if applicable) and how it handles styling (shadow DOM vs. light DOM).

Step 2: Build a Prototype in One Framework

Choose the framework where your team has the most expertise and build a working prototype. Test it thoroughly in that framework before adding others. This prototype will reveal issues with your API design early, when changes are cheap.

Step 3: Add Support for a Second Framework

Integrate the component into a second framework. This is where you will discover most of the cross-framework issues. Pay attention to how the component handles data flow, event propagation, and lifecycle. Document any workarounds you need to apply, as they may indicate a need to adjust your API.

Step 4: Automate Testing Across Frameworks

Set up a CI pipeline that runs tests for each target framework. Use tools like Playwright or Cypress to test the component in a real browser environment. Include tests for property changes, event handling, form submission, and accessibility (e.g., keyboard navigation).

Step 5: Create Documentation and Examples

Provide usage examples in each framework. Show how to bind properties, listen to events, and use slots. Include a playground where developers can try the component live. Good documentation reduces the friction of adoption and prevents misuse.

Step 6: Establish a Governance Process

Define who can propose new components, how API changes are reviewed, and how versioning works. Since multiple teams depend on the component library, breaking changes must be communicated and coordinated. Use semantic versioning and consider a deprecation policy for old APIs.

Risks of Choosing Wrong or Skipping Steps

The most common failure mode is overestimating the simplicity of custom elements. Teams often assume that because the standard is native, reuse will be trivial. They skip the API design phase and start coding, only to find that the component does not work well with any framework. The result is a library that no one wants to use, and the organization reverts to framework-specific components.

Another risk is choosing a strategy that does not match the team's skill set. A team with no Web Component experience that tries to build vanilla custom elements will produce buggy, hard-to-maintain code. Conversely, a team that adopts hybrid middleware without understanding the underlying Web Component standards may struggle to debug issues related to shadow DOM encapsulation or event retargeting.

Skipping the prototype phase is a common mistake. Teams that try to build a full library of 20 components before validating with a single component often discover fundamental API flaws that require rewriting all components. Always start with one representative component—preferably one that involves forms, events, and styling—to validate the approach.

Ignoring form participation is a frequent pitfall. Custom elements do not automatically participate in form submission unless they implement the ElementInternals interface. If your reusable component is an input-like element (date picker, color picker, etc.), you must ensure it can be used in a form without requiring framework-specific workarounds. This often means implementing `formAssociated` and `setFormValue`.

Shadow DOM styling can also cause friction. If you use shadow DOM, styles from the parent application will not penetrate the component. This is intentional for encapsulation, but it means you must provide a theming mechanism, such as CSS custom properties or parts. Teams that forget this end up with components that look out of place in every application.

Mini-FAQ: Common Questions About Custom Element Reusability

Q: Do I need polyfills for custom elements in modern browsers?

Most modern browsers (Chrome, Firefox, Safari, Edge) support custom elements natively. However, Safari had a late adoption of some features like `ElementInternals` and `AdoptedStyleSheets`. If you need to support Safari versions before 16.4, you may need a polyfill for those specific features. For most teams, polyfills are not required for basic custom element support.

Q: How do I handle attribute reflection for complex data types?

Custom element attributes are strings by default. For complex data (objects, arrays), you have two options: serialize the data to JSON and reflect it as an attribute, or use properties (JavaScript properties on the DOM element) instead of attributes. Properties are the preferred way to pass complex data because they avoid serialization overhead. You can use the `observedAttributes` static getter and `attributeChangedCallback` for simple string attributes, but for everything else, use properties and document that consumers must set properties, not attributes.

Q: Can I use custom elements with server-side rendering (SSR)?

Custom elements are primarily a client-side technology. For SSR, you need to render the component's shadow DOM content on the server and then hydrate it on the client. Frameworks like Lit provide SSR support via `@lit-labs/ssr`, but it is still experimental. If SSR is a hard requirement, evaluate whether your component can be rendered as light DOM or if you can defer interactive components to client-side only.

Q: How do I test custom elements across frameworks?

Use a browser-based test runner like Playwright or Cypress. Write tests that load the component in a plain HTML page (no framework) to verify core functionality. Then write additional tests that use the component within each target framework to catch integration issues. Avoid testing framework-specific wrappers in isolation; always test the actual component inside the framework's rendering context.

Q: Should I use shadow DOM or light DOM?

Shadow DOM provides style encapsulation and DOM isolation, which is useful for reusable components that should not be affected by global styles. However, it adds complexity: you cannot use global CSS, and you must use CSS custom properties for theming. Light DOM components are simpler and easier to style from the outside, but they risk style conflicts. A common compromise is to use shadow DOM for complex components (modals, date pickers) and light DOM for simple presentational components (buttons, badges).

Q: What about performance? Do custom elements add overhead?

Custom elements themselves have minimal overhead—they are just HTML elements with a class. The performance cost comes from the rendering library you use inside the component (e.g., Lit's reactive update cycle) and from the work done in `connectedCallback` and `attributeChangedCallback`. For most UI components, the overhead is negligible. However, if you are building a component that renders thousands of items (like a virtual scroller), you need to be careful about update efficiency. Lit and similar libraries use batched updates and efficient DOM diffing, which are generally faster than manual DOM manipulation.

Recommendation Recap Without Hype

Custom elements are a viable strategy for sharing components across frameworks, but they are not a silver bullet. The Flumegro Lens suggests that the most successful implementations start with a clear API spec, validate with a single component across two frameworks, and choose an approach that matches the team's expertise and the organization's long-term goals.

For most teams, hybrid middleware like Lit offers the best balance of developer experience, maintainability, and cross-framework compatibility. It reduces boilerplate without locking you into a proprietary runtime, and the output is standard custom elements. If your team is small and has deep Web Component knowledge, vanilla custom elements can work, but be prepared for a slower development cycle. Framework wrappers are best reserved for organizations with a dedicated platform team that can maintain them across framework upgrades.

Next steps: pick one component from your inventory that is used in at least two frameworks. Build a prototype using your chosen approach. Test it in both frameworks with real user interactions. Document the API and the integration steps. If the prototype succeeds, expand to a second component. If it fails, revisit your strategy—perhaps the component is too complex, or the frameworks are too different. The goal is not to build a perfect library on the first try, but to learn what works in your specific context and iterate from there.

Share this article:

Comments (0)

No comments yet. Be the first to comment!