Type Inheritance in Dial
Dial CLI fully supports TypeScript's type system, including interface inheritance, type intersections, and utility types. This allows you to create reusable, composable type definitions while maintaining all Dial annotations and configurations.
Interface Inheritance
When interfaces extend others, all properties and group configurations are inherited:
Basic Interface Extension
// Base interface with common properties
interface BaseProps {
/**
* Unique identifier
* @dial common
* @dial-dtype string
*/
id: string;
/**
* Visibility control
* @dial common
* @dial-dtype boolean
*/
visible: boolean;
}
// Extended interface inherits all base properties
interface BoxProps extends BaseProps {
/**
* Box dimensions
* @dial geometry
* @dial-dtype vector3
*/
size: [number, number, number];
/**
* Material color
* @dial appearance
* @dial-dtype color
*/
color: string;
}
// BoxProps will have: id, visible, size, color
Deep Inheritance Chains
interface Level1 {
/** @dial level1 */
prop1: string;
}
interface Level2 extends Level1 {
/** @dial level2 */
prop2: number;
}
interface Level3 extends Level2 {
/** @dial level3 */
prop3: boolean;
}
// Level3 has all properties from Level1, Level2, and its own
Type Intersections
Type intersections combine multiple types while preserving all annotations and group configurations:
Basic Type Intersection
type TransformType = {
/** @dial transform @dial-dtype vector3 */
position: [number, number, number];
/** @dial transform @dial-dtype euler */
rotation: [number, number, number];
};
type AppearanceType = {
/** @dial appearance @dial-dtype color */
color: string;
/** @dial appearance @dial-dtype number @dial-min 0 @dial-max 1 */
opacity: number;
};
// Combine multiple types
type GameObject = TransformType & AppearanceType & {
/** @dial metadata @dial-dtype string */
name: string;
};
Group Configuration Inheritance
The @dial-no-wrap configuration is inherited through type intersections:
/**
* Animation properties that should display on one line
* @dial animation @dial-no-wrap
*/
type AnimationType = {
/** @dial animation @dial-dtype number @dial-min 0 @dial-max 10 */
duration: number;
/** @dial animation @dial-dtype string */
easing: string;
/** @dial animation @dial-dtype number @dial-min 0 @dial-max 5 */
delay: number;
};
// Properties from AnimationType inherit noWrap: true
type AnimatedObject = BaseType & AnimationType & {
/** @dial control @dial-dtype boolean */
playing: boolean;
};
Utility Types
Dial supports TypeScript's built-in utility types:
Pick - Select Specific Properties
type FullTransform = {
/** @dial position @dial-dtype number */
x: number;
/** @dial position @dial-dtype number */
y: number;
/** @dial position @dial-dtype number */
z: number;
/** @dial rotation @dial-dtype number-deg */
rotationX: number;
/** @dial rotation @dial-dtype number-deg */
rotationY: number;
/** @dial rotation @dial-dtype number-deg */
rotationZ: number;
};
// Only position properties
type Position2D = Pick<FullTransform, 'x' | 'y'>;
// Result: { x: number, y: number } with annotations
Omit - Exclude Properties
// Everything except rotation
type TranslateOnly = Omit<FullTransform, 'rotationX' | 'rotationY' | 'rotationZ'>;
// Result: { x, y, z } with all dial annotations preserved
Partial - Make Properties Optional
type RequiredConfig = {
/** @dial config @dial-dtype string */
apiKey: string;
/** @dial config @dial-dtype string */
endpoint: string;
/** @dial config @dial-dtype number */
timeout: number;
};
// All properties become optional
type OptionalConfig = Partial<RequiredConfig>;
// Result: { apiKey?: string, endpoint?: string, timeout?: number }
Property Override
Child types can override parent property annotations:
interface BaseComponent {
/**
* Base color property
* @dial appearance
* @dial-dtype string
*/
color: string;
}
interface AdvancedComponent extends BaseComponent {
/**
* Enhanced color with picker
* @dial appearance
* @dial-dtype color
* @dial-icon Palette
*/
color: string; // Overrides with color picker
}
Complex Example
Here's a real-world example combining multiple inheritance patterns:
// Base types with group configs
type PhysicsProps = {
/** @dial physics @dial-dtype number @dial-min 0 @dial-max 100 */
mass: number;
/** @dial physics @dial-dtype number @dial-min 0 @dial-max 1 */
friction: number;
};
/**
* @dial animation @dial-no-wrap
*/
type AnimationProps = {
/** @dial animation */
duration: number;
/** @dial animation */
loop: boolean;
};
// Interface extending a type intersection
interface GameObject extends PhysicsProps, AnimationProps {
/** @dial metadata */
id: string;
/** @dial transform @dial-dtype vector3 */
position: [number, number, number];
}
// Further composition
type InteractiveGameObject = GameObject & {
/** @dial interaction */
onClick: () => void;
/** @dial interaction @dial-dtype boolean */
hoverable: boolean;
};
// Using utility types
type StaticObject = Omit<InteractiveGameObject, 'onClick' | 'hoverable'>;
type PreviewObject = Partial<InteractiveGameObject>;
Generated Schema
The dial-cli correctly resolves all inheritance and generates complete schemas:
{
"component": "InteractiveGameObject",
"schemas": [
{ "name": "mass", "dtype": "number", "min": 0, "max": 100, "tags": { "grouping": "physics" } },
{ "name": "friction", "dtype": "number", "min": 0, "max": 1, "tags": { "grouping": "physics" } },
{ "name": "duration", "dtype": "number", "tags": { "grouping": "animation", "noWrap": true } },
{ "name": "loop", "dtype": "boolean", "tags": { "grouping": "animation", "noWrap": true } },
{ "name": "id", "dtype": "string", "tags": { "grouping": "metadata" } },
{ "name": "position", "dtype": "vector3", "tags": { "grouping": "transform" } },
{ "name": "hoverable", "dtype": "boolean", "tags": { "grouping": "interaction" } }
],
"groups": [
{ "name": "animation", "noWrap": true }
]
}
Best Practices
- Use Base Interfaces for common properties across components
- Group Related Types with type aliases for reusability
- Apply Group Configs at the type level for consistent layout
- Document Overrides when child types change parent behavior
- Test Inheritance by running dial-cli with
--verboseto see full resolution
Limitations
- Generic types with type parameters require concrete types for dial-cli to process
- Conditional types are not fully supported
- Mapped types need explicit property definitions
Next Steps
- Learn about Group Configurations for layout control
- Explore Advanced Annotations for complex types
- See Complete Examples using inheritance patterns