mithril-materialized
Mithril Materialized is a lightweight, TypeScript-first component library that brings Material Design to Mithril.js applications without any external JavaScript dependencies. It provides a comprehensi
Files1
View on GitHubSKILL.md.claude/skills/mithril-materialized/SKILL.md
GitHubMithril Materialized UI Development
Develop and maintain the mithril-materialized repository: a TypeScript Mithril.js Material Design component library with no external JavaScript UI dependencies. This is a contributor skill, not an application-integration guide.
Do not use it for a generic Mithril application that neither uses nor modifies this library. The installable application-integration skill lives in skills/mithril-materialized/.
Project Structure
Monorepo with pnpm workspaces:
packages/lib/— Core library (npm:mithril-materialized), source and entry Sass files insrc/, component Sass partials insass/packages/example/— Documentation site with live component demosdocs/— Generated GitHub Pages assets and TypeDoc output; commit regenerated content when a release requires it
Key files: src/types.ts (shared types), src/utils.ts (uniqueId, helpers), src/index.ts (all exports).
Core patterns
Components that need lifecycle state use Mithril's factory pattern. Keep state inside the factory and return lifecycle methods:
export const MyComponent: FactoryComponent<MyAttrs> = () => {
const state = { id: uniqueId() };
return {
onremove: () => cleanup(),
view: ({ attrs }) => m('.my-component', render(attrs, state)),
};
};
Controlled vs uncontrolled
- Controlled: Parent provides
value+oninput/onchange - Uncontrolled: Use
defaultValue, component tracks state internally - Follow the component's existing
isControlledcontract; do not assume every component uses the same value or handler property
ValidatorFunction<T> returns true | false | '' | string. Always validate on blur, track hasInteracted, integrate with HTML5 setCustomValidity.
Component catalogue
The table is a routing guide, not the API source of truth. Before using an unfamiliar component, inspect its export and attrs in packages/lib/src/index.ts and the component's source file.
| Category | Components | Files |
|---|---|---|
| Text & numeric inputs | TextInput, TextArea, NumberInput, PasswordInput, EmailInput, UrlInput, ColorInput, RangeInput, FileInput, CharacterCounter | input.ts |
| Form structure | FormSection, Fieldset | form-section.ts |
| Choices & toggles | Select, RadioButton(s), Switch, ToggleButton, ToggleGroup, LikertScale, Rating | select.ts, radio.ts, switch.ts, toggle-*.ts |
| Search & file inputs | AutoComplete, Combobox, SearchSelect, Chips, FileUpload | autocomplete.ts, combobox.ts, search-select.ts, file-upload.ts |
| Buttons | Button, LargeButton, SmallButton, FlatButton, IconButton, RoundIconButton, SubmitButton, ConfirmButton, FloatingActionButton | button.ts, floating-action-button.ts |
| Ranges & pickers | SingleRangeSlider, DoubleRangeSlider, DatePicker, TimePicker, TimeRangePicker, AnalogClock, DigitalClock | range-slider.ts, datepicker.ts, timepicker.ts |
| Feedback & overlays | ModalPanel, Dialog, AlertDialog, Menu, ContextMenu, CommandPalette, SnackbarQueue, Tooltip, Toast, Badge, Skeleton, EmptyState, CircularProgress, LinearProgress | modal.ts, dialog.ts, menu.ts, command-palette.ts, snackbar.ts |
| Navigation & organisation | Sidenav, Breadcrumb, Tabs, Pagination, Collapsible, Collection, Dropdown, Wizard | sidenav.ts, tabs.ts, wizard.ts |
| Data & visual layout | Avatar, AvatarGroup, DataTable, VirtualList, TreeView, Masonry, ImageList, Timeline, Carousel, Parallax | avatar.ts, datatable.ts, virtual-list.ts, treeview.ts, masonry.ts |
Theming
Light/dark/auto colors use CSS custom properties (--mm-primary-color, --mm-surface-color, etc.) and ThemeManager. The opt-in Compact Minimal density preset is exported as mithril-materialized/presets/compact-minimal.css and activated with data-mm-preset="compact-minimal" on the document root. Color theme and density are independent; keep every preset rule root-scoped and preserve coarse-pointer targets.
Version 4 layout contract
SearchSelect, FileUpload, LikertScale, Rating, SingleRangeSlider, and DoubleRangeSlider default to className: 'col s12'. Examples must place them in a .row, use an explicit grid width when needed, and avoid duplicate nested column gutters. className: '' intentionally opts out.
Development workflow
pnpm start # Dev servers (lib + example)
pnpm --dir packages/lib test # Unit tests
pnpm --dir packages/lib build:domain # Library build + TypeDoc → docs/typedoc
pnpm build:example # Example site → docs/
For a normal change, update the component, its Sass, focused tests, and the live example when it clarifies behaviour. Run the narrow test first, then the relevant build.
Releases
Releases are created by GitHub Actions on pushes to master, using semantic-release. Use Conventional Commit syntax: fix(scope): ... creates a patch release and feat(scope): ... creates a minor release. The workflow builds the library and example, runs tests, then commits release metadata and generated docs. Do not use the legacy local patch-release or minor-release scripts unless explicitly asked.
Adding a Component
- Create
packages/lib/src/my-component.tsusing FactoryComponent pattern - Export from
packages/lib/src/index.ts - Add styles to relevant
.scssfile - Add demo in
packages/example/src/ - Verify: both modes work, light/dark themes, keyboard navigation
Common Pitfalls
- Support both controlled and uncontrolled modes
- Validate on blur, not input — track
hasInteracted - Use
uniqueId()for element IDs - Clean up in
onremoveto prevent leaks - Never modify
attrs.valuedirectly - Always import CSS:
import 'mithril-materialized/index.css'