Add HTML Component Spec
Create or update an HTML component specification in packages/html/src/{component}/.
When to use
When the user asks to create a new component spec, add new states or options to a component, modify the HTML structure or class names of a component, or scaffold the HTML package portion of a new component. Not for visual tests (use manage-html-tests) or templates (use manage-html-templates).
Inputs
| Input | Required | Description |
|---|---|---|
| Component name | Yes | kebab-case (e.g., chip, info-bar, smart-badge) |
| HTML element | No | Root HTML element (div, span, button, nav). Default: div |
| States | No | e.g., hover, focus, active, disabled, selected. Default: [hover, focus, active, disabled] |
| Options | No | Which option axes to support: size, fillMode, themeColor, rounded. Default: [size, rounded, fillMode, themeColor] |
| Component-specific props | No | Additional props beyond options and states (e.g., icon, text, showAvatar) |
| Description | No | One sentence describing the component |
| Dependencies | No | Other HTML components this imports (e.g., Icon, Avatar) |
If the user doesn't specify states/options, use reasonable defaults based on similar components. Look at an existing component of similar complexity for reference.
Procedure
Step 1: Derive naming constants
From the component name (e.g., smart-badge), derive:
| Derived Name | Example |
|---|---|
| CSS class | k-smart-badge |
| Classname constant | SMART_BADGE_CLASSNAME |
| Folder/module constant prefix | SMART_BADGE |
| TypeScript component name | SmartBadge |
| Module/folder name string | "smart-badge" |
Step 2: Research a reference component
Find an existing component of similar complexity in packages/html/src/ and use it as a structural reference:
- Simple (no states, few options):
badge,avatar - Medium (states + options):
button,chip,card - Complex (sub-components, multiple specs):
grid,toolbar,scheduler
Read the reference spec to understand the patterns used.
Step 3: Create constants.ts
Create packages/html/src/{component}/constants.ts:
export const {COMPONENT_UPPER}_MODULE_NAME = '{component}';
export const {COMPONENT_UPPER}_FOLDER_NAME = {COMPONENT_UPPER}_MODULE_NAME;
Step 4: Create {component}.spec.tsx
Create packages/html/src/{component}/{component}.spec.tsx with the following structure:
Imports — import helpers from ../misc and types from ../_types/component:
import { classNames, optionClassNames, stateClassNames, States, Size, Roundness, FillMode, ThemeColor } from '../misc';
import { KendoComponent, KendoBaseProps } from '../_types/component';
import { {COMPONENT_UPPER}_FOLDER_NAME, {COMPONENT_UPPER}_MODULE_NAME } from './constants';
Classname constant:
export const {COMPONENT_UPPER}_CLASSNAME = `k-{component}`;
States array — include only states the component actually supports:
const states = [
States.hover,
States.focus,
States.active,
States.disabled
] as const;
Options object — include only option axes the component supports. Use the enum values from ../misc:
const options = {
size: [ Size.undefined, Size.small, Size.medium, Size.large ],
rounded: [ Roundness.undefined, Roundness.none, Roundness.small, Roundness.medium, Roundness.large, Roundness.full ],
fillMode: [ FillMode.undefined, FillMode.solid, FillMode.flat, FillMode.outline ],
themeColor: [ ThemeColor.undefined, ThemeColor.base, ThemeColor.primary, ThemeColor.secondary, ThemeColor.tertiary, ThemeColor.info, ThemeColor.success, ThemeColor.warning, ThemeColor.error ],
};
Type definitions — define Options, Props, and State types:
export type Kendo{ComponentName}Options = {
size?: (typeof options.size)[number] | null;
rounded?: (typeof options.rounded)[number] | null;
fillMode?: (typeof options.fillMode)[number] | null;
themeColor?: (typeof options.themeColor)[number] | null;
};
export type Kendo{ComponentName}Props = Kendo{ComponentName}Options & {
// Add component-specific props here
};
export type Kendo{ComponentName}State = { [K in (typeof states)[number]]?: boolean };
Default options:
const defaultOptions = {};
Component function — renders the HTML with proper classNames:
export const {ComponentName}: KendoComponent<Kendo{ComponentName}Props & Kendo{ComponentName}State & React.HTMLAttributes<HTMLDivElement>> = (
props: Kendo{ComponentName}Props &
Kendo{ComponentName}State &
KendoBaseProps &
React.HTMLAttributes<HTMLDivElement>
) => {
const {
as: Component = 'div',
size,
rounded,
fillMode,
themeColor,
hover,
focus,
active,
disabled,
className,
...other
} = props;
return (
<Component
className={classNames(
className,
{COMPONENT_UPPER}_CLASSNAME,
optionClassNames({COMPONENT_UPPER}_CLASSNAME, {
size,
rounded,
fillMode,
themeColor,
}),
stateClassNames({COMPONENT_UPPER}_CLASSNAME, {
hover,
focus,
active,
disabled,
})
)}
{...other}
>
{props.children}
</Component>
);
};
Static properties — attach metadata to the component function:
{ComponentName}.states = states;
{ComponentName}.options = options;
{ComponentName}.className = {COMPONENT_UPPER}_CLASSNAME;
{ComponentName}.defaultOptions = defaultOptions;
{ComponentName}.moduleName = {COMPONENT_UPPER}_MODULE_NAME;
{ComponentName}.folderName = {COMPONENT_UPPER}_FOLDER_NAME;
export default {ComponentName};
ARIA spec — add an ariaSpec property. The ariaSpec object on the spec component is the single source of truth for ARIA testing:
{ComponentName}.ariaSpec = {
selector: '.k-{component}',
rules: []
};
Step 5: Create index.ts
Create packages/html/src/{component}/index.ts:
export * from './{component}.spec';
This file will be extended by the manage-html-templates skill when templates are added.
Step 6: Register in main index (new components only)
For new components, add the export to packages/html/src/index.ts:
export * from './{component}';
Place it alphabetically among existing exports.
Step 7: Validate
npm run typecheck --prefix packages/html 2>&1
Fix any TypeScript errors before proceeding.
Updating an existing spec
When updating an existing spec (adding states, options, or props):
- Read the existing spec to understand its current structure
- Add to the
statesarray if adding new states - Add to the
optionsobject if adding new option values - Update the type definitions to include new props
- Update the render function destructuring to handle new props
- Update classNames if new props affect CSS classes
- Update
defaultOptionsif new props have defaults - Don't change the component's static property assignments unless adding new properties
Gotchas
- Only edit
.tsxsource files inpackages/html/src/. Never edit the compiled.htmlfiles in the roottests/folder — those are auto-generated build output. - Import helpers from
../misc— this module exportsclassNames,optionClassNames,stateClassNames,States,Size,Roundness,FillMode,ThemeColor, and other enums. - Import
KendoComponentandKendoBasePropsfrom../_types/component. - The
asprop (viaKendoBaseProps) allows changing the root element at usage time. - Use
{...other}spread to forward HTML attributes to the root element. - States are boolean props (
hover,focus, etc.) — they add CSS state classes, not pseudo-classes. - The
classNameprop must be merged viaclassNames()— don't overwrite it. - Use
optionClassNamesfor options (generatesk-{component}-{option}-{value}) andstateClassNamesfor states (generatesk-{component}-{state}). - Static properties (
states,options,className, etc.) on the component function are required — they're used by the test and build systems. - The
moduleNameandfolderNamemust match the folder name inpackages/html/src/. - Only include option values that the SCSS theme actually supports — don't add
ThemeColor.inverseif the component has no inverse styles. - The spec file is the single source of truth for the component's HTML output — templates and tests depend on it.