Create Modus Wrapper Component
Scaffold a new Modus wrapper component following established SolidJS patterns from the codebase.
When to Use
Use this skill when:
- Creating a new wrapper component for a Modus web component
- You need to integrate a Modus component that doesn't have a wrapper yet
- You want to ensure proper event handling and TypeScript types
Pattern Overview
All Modus wrapper components in SolidJS follow this structure:
- Use vanilla web component (
<modus-wc-[component]>) from@trimble-oss/moduswebcomponents - Define TypeScript props interface with JSDoc comments
- Use
let reffor component reference (callback ref pattern) - Use
createEffectfor event listeners with proper cleanup - Forward props to the web component with conditional spreading
- Handle events via event listeners, not SolidJS props
Reference Examples
- Simple wrapper:
src/components/ModusButton.tsx- No event listeners needed - With event listeners:
src/components/ModusCheckbox.tsx- Shows event handling pattern - With dropdown:
src/components/ModusDropdownMenu.tsx- Shows menu event handling
Implementation Template
import { createEffect } from "solid-js";
import type { Component } from "solid-js";
export interface Modus[ComponentName]Props {
/** Description of prop */
propName?: string;
/** A callback function to handle events. */
onEventName?: (event: CustomEvent<EventDetailType>) => void;
/** A custom CSS class to apply to the component. */
customClass?: string;
/** The ARIA label for accessibility. */
"aria-label"?: string;
}
/**
* Renders a Modus [component name] component.
*
* @example
* // Basic usage
* <Modus[ComponentName] propName="value" />
*
* @example
* // With event handler
* <Modus[ComponentName]
* propName="value"
* => console.log(event.detail)}
* />
*/
const Modus[ComponentName]: Component<Modus[ComponentName]Props> = (props) => {
let componentEl: HTMLModusWc[ComponentName]Element | undefined;
createEffect(() => {
const component = componentEl;
if (!component) return;
const handleEventName = (event: Event) => {
props.onEventName?.(event as CustomEvent<EventDetailType>);
};
if (props.onEventName) {
component.addEventListener("eventName", handleEventName);
}
return () => {
if (props.onEventName) {
component.removeEventListener("eventName", handleEventName);
}
};
});
return (
<modus-wc-[component-name]
ref={(el) => (componentEl = el)}
prop-name={props.propName}
custom-class={props.customClass}
aria-label={props["aria-label"]}
/>
);
};
export default Modus[ComponentName];
Key Patterns
1. Conditional Prop Spreading
For optional props that shouldn't be passed when undefined:
// ✅ CORRECT: Conditional spreading
<modus-wc-component
{...(props.color && { color: props.color })}
{...(props.variant && { variant: props.variant })}
size={props.size}
/>
2. Event Listener Setup
Always use createEffect with cleanup:
createEffect(() => {
const component = componentEl;
if (!component) return;
const handleEvent = (event: Event) => {
props.onEvent?.(event as CustomEvent<EventDetailType>);
};
if (props.onEvent) {
component.addEventListener("eventName", handleEvent);
}
return () => {
if (props.onEvent) {
component.removeEventListener("eventName", handleEvent);
}
};
});
3. TypeScript Types
Use proper types for web component elements:
// ✅ CORRECT: Proper element type
let componentEl: HTMLModusWcButtonElement | undefined;
let componentEl: HTMLModusWcCheckboxElement | undefined;
let componentEl: HTMLModusWcDropdownMenuElement | undefined;
// Pattern: HTMLModusWc[ComponentName]Element
4. Prop Naming
Web components use kebab-case for props:
// SolidJS prop: customClass
// Web component prop: custom-class
<modus-wc-component custom-class={props.customClass} />
// SolidJS prop: modalId
// Web component prop: modal-id
<modus-wc-modal modal-id={props.modalId} />
5. Callback Ref Pattern
<modus-wc-component ref={(el) => (componentEl = el)} />
Common Event Names
Modus components use these common event names:
inputChange- For input value changesinputFocus- For focus eventsinputBlur- For blur eventsbuttonClick- For button clicksitemSelect- For menu/dropdown item selectionmenuVisibilityChange- For dropdown menu visibilityexpandedChange- For accordion/collapse state
Check the Modus documentation for component-specific events.
Accessibility
Always include:
aria-labelprop for icon-only or non-text components- Proper ARIA attributes passed to web component
- Keyboard navigation support (usually handled by web component)
Testing Checklist
- Component renders without errors
- Props are forwarded correctly to web component
- Event listeners are set up and cleaned up properly
- TypeScript types are correct
- Accessibility attributes are included
- Conditional props don't pass undefined values
Common Mistakes to Avoid
- Missing cleanup: Always return cleanup function from
createEffect - Wrong event names: Check Modus docs for exact event names
- Passing undefined: Use conditional spreading for optional props
- Wrong prop names: Web components use kebab-case, SolidJS uses camelCase
- Missing null checks: Always check
componentElbefore use