# Component API Smells This document catalogs potential complaints about the current component/template API. Each item includes: - Problem - Why it hurts - Potential solutions - Consequences/costs ## 1) Magic Strings for Variants, Sizes, Types, Directions Problem: - Many components use raw strings for semantic options (`variant`, `size`, `type`, `direction`, `align`, etc.). - Invalid values often silently fall back to defaults. Why it hurts: - No compile-time safety. - Typos are easy to miss. - Weak IntelliSense discoverability. Potential solutions: - Replace string options with enums for common semantic domains. - Generate constants classes per component for non-breaking intermediate step. - Add analyzers that validate allowed literals where strings are retained. Consequences/costs: - Enums can be breaking if public signatures change. - Constants are low cost but do not fully prevent invalid values. - Analyzer route adds tooling complexity. ## 2) Inconsistent Styling Extensibility (`extraClasses` and wrappers) Problem: - Some components have `extraClasses`; others do not. - Developers often wrap components in outer `div` just to apply layout/styling. Why it hurts: - No consistent mental model. - Extra wrapper markup increases noise and nesting depth. Potential solutions: - Add a standard `className` (or `extraClasses`) parameter to every component. - Support class merging utility behavior in a shared helper. Consequences/costs: - Public constructor expansion across many components. - Need policy for precedence (base classes first vs custom classes first). ## 3) Missing Uniform Attribute Pass-Through Problem: - Attribute extensibility is fragmented (`hxAttrs` in some places, none in others). - No first-class support for arbitrary `aria-*`, `data-*`, test IDs, analytics attributes. Why it hurts: - Manual string composition is error-prone. - Difficult accessibility and testing instrumentation. Potential solutions: - Add a shared attributes bag type (`IReadOnlyDictionary`). - Keep `hxAttrs` temporarily as compatibility shim. Consequences/costs: - Larger refactor surface. - Slight allocation/processing overhead. - Requires HTML attribute encoding rules in one central place. ## 4) `hxAttrs` Raw String Footgun Problem: - Raw attribute strings allow malformed markup or accidental injection. Why it hurts: - Hard-to-debug render bugs. - Security posture depends on each caller doing manual encoding correctly. Potential solutions: - Deprecate raw `hxAttrs` in favor of typed/structured attrs. - Provide safe helper methods to construct HTMX attribute sets. Consequences/costs: - Migration needed for existing call sites. - Potentially breaking unless a gradual fallback is kept. ## 5) Unsafe-by-Default Raw HTML Content Paths Problem: - Several components accept string content that is rendered as raw HTML. - Caller must remember to encode user-provided values. Why it hurts: - XSS risk in real application code. - Easy to misuse when moving quickly. Potential solutions: - Safe-by-default encoding for plain string inputs. - Separate APIs for encoded text vs trusted HTML (explicit escape hatch). - Introduce a `SafeHtml` wrapper type for intentional raw HTML. Consequences/costs: - Safe-by-default may break current behavior for callers relying on raw HTML. - Trusted-HTML API adds conceptual complexity, but clearer intent. ## 6) Inconsistent Security Guidance in Component Docs Problem: - Some docs mention encoding; others do not provide clear warnings. Why it hurts: - Security correctness relies on tribal knowledge. Potential solutions: - Add a standardized "Security" section to every component doc. - Include explicit examples: safe input, unsafe input, and fix. Consequences/costs: - Documentation maintenance overhead. - Strong DX/security benefit for low implementation cost. ## 7) No Explicit "Component Props" Model in Markup Problem: - Slots replace placeholders, but there is no direct concept of passing typed props in `.htmx` markup itself. - Dynamic behavior is mostly constructor-centric in `.htmx.cs`. Why it hurts: - Feels unlike modern component systems where props are explicit and local. - New users may expect inline component parameterization and be surprised. Potential solutions: - Document this limitation clearly as a design constraint. - Add a generated props record convention per component/page. - Explore optional parameterized slot syntax in generator (long-term). Consequences/costs: - Props model requires generator design changes. - Parameterized slot syntax is high complexity and may conflict with AOT simplicity. ## 8) Constructor Bloat and Low Readability Problem: - Some components expose many optional parameters, often multiple strings. Why it hurts: - Ambiguous calls and poor self-documentation. - Easy to mis-order arguments. Potential solutions: - Favor required args + options record pattern. - Add fluent builders for complex components. Consequences/costs: - Options records improve readability but introduce extra types. - Builders can increase allocations and complexity. ## 9) Tuple-Based APIs for Complex Components Problem: - Components like tabs/accordion/dropdown/table rely on tuple collections. Why it hurts: - Tuples are easy to misuse and harder to read than named objects. - Harder to evolve APIs without breaking all call sites. Potential solutions: - Replace tuple parameters with named records (`TabItem`, `AccordionItem`, etc.). Consequences/costs: - Migration churn across existing usage. - Clear long-term maintainability win. ## 10) Missing Validation Feedback for Invalid Inputs Problem: - Invalid option values often degrade silently rather than failing fast. Why it hurts: - Bugs are hidden and discovered late. Potential solutions: - Add debug-time validation with clear exceptions/messages. - Optionally emit logs/diagnostics in production with safe defaults. Consequences/costs: - Strict runtime validation can be breaking for existing invalid usages. - Diagnostics-only mode is safer for migration. ## 11) Inconsistent Boolean Option Naming Problem: - Different components use varying naming styles for booleans and toggles. Why it hurts: - Low API predictability. Potential solutions: - Define naming conventions (`isX`, `hasX`, `enableX`) and enforce globally. Consequences/costs: - Rename churn if normalized retroactively. ## 12) CSS Contract Coupled to JS via Hidden Class Names Problem: - Interactive behavior relies on specific class/data selectors that are effectively API contracts. Why it hurts: - Refactoring classes can break behavior. - Coupling is not obvious from constructor APIs. Potential solutions: - Document required selectors/events in each interactive component doc. - Prefer stable `data-component`/`data-role` markers over purely visual class names. Consequences/costs: - Markup updates across components and JS. - Better long-term resilience to style refactors. ## 13) Runtime Behavior Dependencies Not Surfaced in API Problem: - Components requiring JS initialization do not expose that requirement in code signatures. Why it hurts: - Silent "renders but does not work" failures. Potential solutions: - Add `Requires JavaScript` section in docs and XML comments. - Add lightweight marker interface or metadata attribute for interactive components. Consequences/costs: - Minimal runtime cost; mostly documentation/tooling work. ## 14) Accessibility Ergonomics Gaps Problem: - No consistent way to pass `aria-*`, `id`, `for`, `describedby` across all components. Why it hurts: - Accessibility quality depends on manual wrapper hacks. Potential solutions: - Introduce shared accessible options type. - Provide defaults and enforce required labels where relevant. Consequences/costs: - Constructor changes and additional validation logic. ## 15) Testability Friction (No Standard Test IDs) Problem: - No consistent `data-testid` or attribute pass-through strategy. Why it hurts: - E2E selectors become brittle (class/text-based selectors). Potential solutions: - Add standard attributes bag and testing guidance. - Add `testId` convenience parameter in interactive/form primitives. Consequences/costs: - Minor API surface increase. - Significant test stability benefit. ## 16) Documentation Discoverability Gaps Problem: - Component docs focus on usage but under-emphasize known limitations and smell areas. Why it hurts: - New contributors re-learn the same constraints repeatedly. Potential solutions: - Add dedicated docs for limitations, anti-patterns, and migration strategy. - Add an index from component reference into Issues docs. Consequences/costs: - Ongoing documentation upkeep. ## 17) Inconsistent Naming (`extraClasses` vs alternatives) Problem: - Similar concepts have inconsistent parameter names. Why it hurts: - Context switching overhead. Potential solutions: - Standardize naming dictionary and enforce in reviews. - Offer temporary backward-compatible aliases. Consequences/costs: - Alias support increases short-term complexity. ## 18) Lack of Strongly-Typed Domain Primitives Problem: - IDs, route paths, CSS classes, and labels are all plain strings. Why it hurts: - Accidental parameter swaps and weak intent signaling. Potential solutions: - Introduce lightweight value objects or records for high-value domains (`DialogId`, `CssClassList`, etc.). Consequences/costs: - Added type count and conversion code. - Better readability and safer APIs. ## 19) Missing Centralized Class Composition Policy Problem: - Tailwind class strings are composed ad-hoc in constructors. Why it hurts: - Risk of duplicate/conflicting classes. - Hard to audit variant behavior consistency. Potential solutions: - Add shared class composition helper utilities. - Optionally adopt a deterministic merge utility pattern. Consequences/costs: - New utility dependency or internal helper maintenance. ## 20) Limited Error Reporting for Misconfigured Interactive Components Problem: - Missing/incorrect JS hooks often fail quietly. Why it hurts: - Time-consuming debugging. Potential solutions: - Development-only console warnings/assertions from `components.js` when expected markers are missing. Consequences/costs: - Slight JS complexity increase. - Better troubleshooting experience. ## 21) Form Component API Inconsistency Problem: - Form primitives vary in how they accept value/default/checked/attrs/labels. Why it hurts: - Hard to predict usage patterns across components. Potential solutions: - Define a shared form control contract: - `name`, `id`, `label`, `value`, `disabled`, `required`, `className`, `attributes` Consequences/costs: - Widespread API harmonization work. - Major usability win once stabilized. ## 22) No First-Class Validation/Error State Patterns Problem: - Error display, invalid styling, and message linkage are largely ad-hoc. Why it hurts: - Inconsistent UX and accessibility for validation states. Potential solutions: - Add canonical form-field wrapper component and error semantics. - Add helper patterns in docs for mapping server validation to components. Consequences/costs: - Additional abstractions and migration. ## 23) Table API Lacks Strong Cell/Column Models Problem: - Table inputs as nested strings are simplistic and rigid. Why it hurts: - Hard to represent links, badges, actions, and per-cell semantics safely. Potential solutions: - Introduce column and row models with typed cell renderers. - Support text cell vs trusted HTML cell explicit APIs. Consequences/costs: - Significant redesign effort for table API. - High payoff for real-world usage. ## 24) Potential Over-Eager Precomputation in Constructors Problem: - Some components precompute heavy HTML payloads in constructor. Why it hurts: - Allocation spikes for large datasets. - Can surprise developers expecting render-time streaming. Potential solutions: - Lazy compute/cache expensive sections. - Document performance profile and guardrails per component. Consequences/costs: - Possible complexity in caching invalidation. - Better performance transparency. ## 25) No Compatibility Policy for API Evolution Problem: - No explicit deprecation policy for parameter renames or behavior changes. Why it hurts: - Contributors hesitate to improve APIs due to break risk. Potential solutions: - Define semver/deprecation policy in docs. - Use staged migration with obsolete annotations. Consequences/costs: - Process overhead, but critical for long-term maintainability. ## 26) No Unified Component Design Principles Doc Problem: - Patterns are documented, but not as enforceable design principles. Why it hurts: - New components may diverge in API style. Potential solutions: - Publish a component API style guide with mandatory rules and preferred patterns. Consequences/costs: - Requires reviewer discipline. ## 27) Internationalization (i18n) Boundaries Not Explicit Problem: - Many labels/content are plain strings without explicit localization guidance. Why it hurts: - Inconsistent localization strategy across pages/components. Potential solutions: - Add docs for localizable boundaries and resource integration patterns. Consequences/costs: - Documentation and integration work. ## 28) Missing "Known Limitations" Section in Component Reference Entry Point Problem: - The main component reference does not prominently call out systemic limitations. Why it hurts: - Developers discover constraints by trial and error. Potential solutions: - Add up-front limitations and issue tracker links in component reference. Consequences/costs: - Low cost; immediate discoverability gains. ## 29) API Surface Differs Across Similar Components Problem: - Similar categories (display/form/interactive) do not expose comparable extension points. Why it hurts: - Surprising differences force re-learning per component. Potential solutions: - Define a baseline component contract by category: - Display: `className`, `attributes` - Form: baseline form control props + attrs - Interactive: baseline + JS contract notes Consequences/costs: - Requires systematic API audit and staged rollout. ## 30) Missing Tooling Support for API Misuse Problem: - No analyzers/code fixes for common mistakes (invalid variant, unsafe content, missing encoding). Why it hurts: - Review burden remains manual. Potential solutions: - Introduce Roslyn analyzers for: - magic string validation - unsafe raw HTML from untrusted sources - missing serialization registration patterns Consequences/costs: - Initial tooling investment is medium-high. - Scales quality across the codebase after adoption. --- ## Cross-Cutting Improvement Patterns 1. Standardized base options record: - `className` - `attributes` - `testId` - `ariaLabel` 2. Strong typing for semantic options: - enums/constants/analyzers 3. Safe content model: - text-safe by default, explicit trusted-html escape hatch 4. Better docs contract: - every component doc should include: - security notes - accessibility notes - JS dependency notes (if interactive) - extension points 5. Migration strategy: - additive changes first - obsolete old params - remove deprecated paths in major version bump