This file provides guidance to AI coding agents (like Claude Code, GitHub Copilot, etc.) when working with code in this repository.
@disciple.tools/web-components is a library of standard web components built with Lit. These components are used across the Disciple.Tools ecosystem.
- Framework: Lit (v3)
- Language: JavaScript (ES modules)
- Documentation/Demo: Storybook
- Testing: @web/test-runner with @open-wc/testing
- Build Tool: Vite
- Localization: @lit/localize
npm install # Install dependencies
npm start # Start Vite dev server for local demo
npm run build # Build components for production (dist/)
npm run analyze # Generate custom-elements.json manifestnpm run storybook # Start local Storybook instance (port 8080)
npm run storybook:build # Build static Storybook sitenpm run lint # Run ESLint and Prettier checks
npm run format # Fix linting and formatting issuesnpm run test # Run tests once with coverage
npm run test:watch # Run tests in watch modenpm run localize:extract # Extract strings from code to XLIFF files
npm run localize:build # Build localized JS templates from XLIFF- Standards: Strictly follow the Standards.md file for component structure and implementation details.
- Base Classes:
- Most form components should extend
DtFormBase(fromsrc/components/form/dt-form-base.js). - Generic components should extend
DtBase(fromsrc/components/dt-base.js).
- Most form components should extend
- Component Files: A standard component directory (e.g.,
src/components/form/dt-text/) includes:*.js: The component implementation.*.stories.js: Storybook stories.*.test.js: Unit tests.docs.mdx: Component documentation.
- Icons: Use
this.renderIcons()(available inDtFormBase) instead of manual icon tags where possible. - Localization: Use the
msg()function from@lit/localizefor all user-facing strings. - Responsive Design: Ensure components work well for touch, mouse, and keyboard input and that they scale appropriately on different screen sizes.
- Indentation: 2 spaces (Standard for JS/Lit projects).
- Naming:
- Components:
dt-component-name(custom element tag),DtComponentName(class name). - Files:
dt-component-name.js.
- Components:
- Properties: Use camelCase for properties in JS and kebab-case for attributes in HTML.
- Documentation: Use JSDoc for properties and methods.
- Don't add JavaScript when CSS or HTML can solve the problem
- Keep markup clean and readable
- Use CSS variables from base classes (
--primary-color) and base CSS file instead of hard-coded values. - For each component, inherit CSS variables from its base class (
DtFormBase) and create additional CSS variables for component-specific styles- Example: in
dt-text, a border color should have a component-specific variable that falls back to the default:var(--dt-text-border-color, var(--dt-form-border-color));
- Example: in
- When editing an existing component, update the CSS variables according to the above 2 guidelines.
- After any variables are changed in a component, update the
docs.mdxfile to reflect the changes.
- After any variables are changed in a component, update the
- If the render function of a component uses input, select, or any other standard form elements, add a part attribute to it for advanced styling.
- Mobile-first responsive approach
- Ensure all styles support touch input
- Ensure keyboard navigation support
- Use tab index when appropriate to ensure keyboard navigation flows logically
- Prefer modern CSS (Grid, Flexbox, custom properties) over hacks
- Use relative units (rem, em) for typography and spacing
- Avoid !important except for utility overrides
- Keep specificity low and manageable
- For form components, set the title to be in the Components/Form/ directory, and use a human-friendly name for its title
- When writing stories, use the standard JSON object notation instead of Template.bind syntax.
export const EnteredValue = {
args: {
value: 'Lorem Ipsum',
},
};- Use the render function to define the component markup and properties.
- Use
ifDefined()to prevent rendering of non-boolean properties that are undefined.<dt-text id=${ifDefined(id)} - Ensure at least all these standard properties are included and rendered:
- id
- name
- label
- value
- disabled
- required
- requiredMessage
- icon
- iconAltText
- private
- This should replace isPrivate
- When used in the HTML, it should be referenced as args.private and not destructured
- privateLabel
- loading
- saved
- error
- slot
- onChange
- This should be set to
action('on-change')
- This should be set to
- Set default values for the following properties:
id = 'name',name = 'field-name',label = 'Field Name',icon = 'https://cdn-icons-png.flaticon.com/512/1077/1077114.png',iconAltText = 'Icon Alt Text',
- The render function should not include a style tag with the theme CSS
- For all component events, ensure the
action()function from@storybook/addon-actionsis used to log the event details to the Actions panel- Specify defaults for all events in the args of the component's story definition:
args: { onChange: action('on-change') }
- Specify defaults for all events in the args of the component's story definition:
- Use
- Ensure that at least these standard stories are included at a minimum. There may be additional stories that are specific to the component's functionality beyond these:
- Empty
- No value set
- EnteredValue / SelectedValue
- Value set to an expected value
- The exact name of this may change if the component implements different value types. Just ensure there is a test that includes a pre-set value
- SvgIcon
- Show label icon as an SVG
- The value of the slot should be 'SvgIcon', not actual svg content
- Set
iconto null
- AutoSave
- The value of onChange should be onAutoSave from stories-utils.js
- Disabled
- Set disabled to true
- PrivateField
- Set private to true
- The privateLabel should be set to a meaningful value
- Loading
- Set loading to true
- Shows loading icon
- Saved
- Set saved to true
- Shows saved checkmark icon
- Error
- Set error to sample error message
- ErrorSlot
- Set slot to ErrorSlot
- Used for testing links in the error message
- Set error to '[Should show link here]' to make sure it doesn't show
- BasicForm
- Set FormDecorator
- Set a value
- Used with the action panel to test the value submitted with forms
- Required
- Set FormDecorator
- Set required to true
- Used to ensure a blank value will trigger the required message on form submission
- RequiredCustomMessage
- Set FormDecorator
- Set required to true
- Set requiredMessage to 'Custom error message'
- Used to ensure a blank value will trigger the required message on form submission
- LocalizeRTL
- Use LocaleDecorator
- Set dir to 'rtl'
- Set lang to 'ar'
- Used to ensure RTL styles are applied correctly
- Empty
- Use MDX to write component documentation.
- Use the
docs.mdxfile to document all component features and tech specs - Add sections for:
- Parameters
<Controls />
- Slots
- If there are no custom slots in the component, link to slots from the base class
- Events
- Subheadings for each event type
- Add a description of the event details sent with the event
- CSS Custom Properties
- Link to inherited CSS variables from the base class
- List all custom CSS variables defined in the component along with their default values
- Parts
- List all part names used in the component and a description of their purpose
- Parameters
- See
src/components/form/dt-text/docs.mdxfor an example.
- Use
@open-wc/testingfor fixture-based testing. - Use
@web/test-runner-commandsfor simulating user interaction (typing, clicking). - Ensure tests cover:
- Default state
- Property updates
- Event dispatching (especially
changeevents) - Accessibility (a11y)
- Add tests for any new features. Ensure they fail before the feature is implemented and pass when the feature is complete and enabled.
- Add stories to storybook for any new components or features.
- When testing dispatched events, do not use
oneEvent()since it can often cause a timeout- Instead, declare
let eventDetail = null;before the fixture is defined. - In the fixture, listen for the event and set
eventDetail = e.detail;(e.g.@change="${e => (eventDetail = e.detail)}") - After performing the intended action, test that eventDetail has the expected value.
expect(eventDetail).to.not.be.null;- e.g.
expect(eventDetail.newValue).to.have.members(['opt1','opt2']);
- Instead, declare
- Avoid usage of wait timers such as
aTimeout(100)- Instead, use
await nextFrame()orawait waitUntil()to wait for next animation frame or until a condition is met.
- Instead, use
- Boolean Attributes: Always include
{ type: Boolean }in static properties if the attribute should be a toggle. - Event Dispatching: Use event details to provide change event details
this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value } }))for form values.
this.dispatchEvent(new CustomEvent('change', {
detail: {
field: this.name,
oldValue: this.value,
newValue: e.target.value, // use appropriate logic to determine what the new value is
}
}- Slots: Use standard slots or named slots as defined in
DtFormBase.