Agent skill
modal-components
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/modal-components
SKILL.md
Modal Components Skill
CRITICAL: All modals MUST use the global Modal component with the correct API
Overview
The Diddit! app has a centralized, reusable Modal component at apps/frontend/src/app/components/modals/modal/modal.ts. This component handles ALL modal functionality in one place:
- ✅ Backdrop click to close
- ✅ ESC key to close
- ✅ Close button (X) to close
- ✅ Focus trap
- ✅ Body scroll prevention
- ✅ Accessibility (ARIA attributes, roles)
- ✅ Animations
NEVER reimplement these features. ALWAYS use the global Modal component.
Correct API
Inputs
open = input<boolean>(false); // Whether modal is open
title = input<string>(''); // Modal title text
closeOnBackdropClick = input<boolean>(true); // Allow backdrop close (default: true)
closeOnEsc = input<boolean>(true); // Allow ESC close (default: true)
Outputs
closeModal = output<void>(); // Emitted when modal should close
CRITICAL: The output is closeModal, NOT closeRequested, NOT close, NOT anything else.
Template Pattern
✅ CORRECT Usage
<app-modal [open]="open()" (closeModal)="handleClose()" title="Modal Title">
<!-- Your modal content here -->
<form>
<!-- Form fields -->
</form>
<!-- Action buttons (optional) -->
<div class="modal-actions" modal-actions>
<button type="button" class="btn btn-secondary" (click)="onCancel()">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</app-modal>
Key Points
- Use
[open]input - Pass a signal or boolean to control visibility - Use
(closeModal)output - Listen to the EXACT event namecloseModal - Use
titleinput - Pass title as input property, NOT using slots - Use
modal-actionsattribute - For footer buttons (optional) - Content goes in default slot - Just put content inside
<app-modal>tags
❌ WRONG - Do NOT Do This
<!-- WRONG: Using wrong event name -->
<app-modal [open]="open()" (closeRequested)="handleClose()">
<!-- WRONG: Using slots for title -->
<app-modal [open]="open()" (closeModal)="handleClose()">
<h2 slot="title">Modal Title</h2>
<!-- WRONG: Using old @Output name -->
<app-modal [open]="open()" (close)="handleClose()">
<!-- WRONG: Not binding to signal/property -->
<app-modal open="true" (closeModal)="handleClose()"></app-modal></app-modal></app-modal
></app-modal>
Component TypeScript Pattern
✅ CORRECT Component
import { Component, input, output, signal } from '@angular/core';
import { Modal } from '../modals/modal/modal';
@Component({
selector: 'app-my-modal',
imports: [Modal],
templateUrl: './my-modal.html',
styleUrl: './my-modal.css',
})
export class MyModal {
// Inputs
open = input<boolean>(false);
someData = input<string>('');
// Outputs
closeRequested = output<void>(); // Emit to parent when modal should close
// Internal state
submitting = signal(false);
/**
* Handle modal close (called by Modal's closeModal event)
*/
handleClose(): void {
this.closeRequested.emit();
}
/**
* Handle cancel button
*/
onCancel(): void {
this.closeRequested.emit();
}
/**
* Handle form submission
*/
onSubmit(): void {
// Do work...
this.closeRequested.emit();
}
}
Key Points
- Import Modal component - Add to
importsarray - Accept
openinput - Let parent control visibility - Emit
closeRequestedoutput - Let parent handle closing - Create
handleClose()method - Connect Modal's(closeModal)to yourcloseRequestedoutput - Don't manage open state internally - Parent controls it
Real Working Examples
Study these working modals in the codebase:
add-child-modal
- Location:
apps/frontend/src/app/components/modals/add-child-modal/ - Pattern: Form modal with validation
- Template: Uses
(closeModal),titleinput,modal-actionsslot
invite-modal
- Location:
apps/frontend/src/app/components/modals/invite-modal/ - Pattern: Form modal with role selection
- Template: Uses
(closeModal),titleinput,modal-actionsslot
task-form-modal
- Location:
apps/frontend/src/app/components/modals/task-form-modal/ - Pattern: Complex form with conditional fields, delete confirmation
- Template: Uses
(closeModal), dynamic[title],modal-actionsslot
Common Mistakes
❌ Mistake 1: Wrong Event Name
Problem: Listening for (closeRequested) or (close) instead of (closeModal)
<!-- WRONG -->
<app-modal [open]="open()" (closeRequested)="handleClose()"></app-modal>
Result: Modal won't close when clicking X, backdrop, or pressing ESC
Fix: Use (closeModal)
<!-- CORRECT -->
<app-modal [open]="open()" (closeModal)="handleClose()"></app-modal>
❌ Mistake 2: Using Slots for Title
Problem: Trying to use slot-based API that doesn't exist
<!-- WRONG -->
<app-modal [open]="open()" (closeModal)="handleClose()"> <h2 slot="title">My Title</h2></app-modal>
Result: Title appears in modal body, not header. Empty header shows.
Fix: Use title input
<!-- CORRECT -->
<app-modal [open]="open()" (closeModal)="handleClose()" title="My Title"></app-modal>
❌ Mistake 3: Duplicate Headers
Problem: Setting title on Modal AND adding header in content
<!-- WRONG -->
<app-modal [open]="open()" (closeModal)="handleClose()" title="Add Task">
<h2>Add Task</h2>
<!-- Duplicate header! -->
<form>...</form>
</app-modal>
Result: Two headers appear
Fix: Only use Modal's title input
<!-- CORRECT -->
<app-modal [open]="open()" (closeModal)="handleClose()" title="Add Task">
<form>...</form>
<!-- No duplicate header -->
</app-modal>
Storybook Requirement
CRITICAL: Every modal component MUST have a Storybook story.
Why?
Storybook catches modal bugs immediately:
- Close functionality (X button, backdrop, ESC)
- Header rendering
- Form validation
- Loading states
- Error states
Story Template
// my-modal.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';
import { MyModal } from './my-modal';
const meta: Meta<MyModal> = {
title: 'Components/Modals/MyModal',
component: MyModal,
tags: ['autodocs'],
argTypes: {
open: { control: 'boolean' },
},
};
export default meta;
type Story = StoryObj<MyModal>;
export const Default: Story = {
args: {
open: true,
},
};
export const Closed: Story = {
args: {
open: false,
},
};
export const WithData: Story = {
args: {
open: true,
someData: 'Example data',
},
};
Testing Checklist in Storybook
When viewing your modal story, verify:
- Modal opens when
open: true - Modal closes when clicking X button
- Modal closes when clicking backdrop
- Modal closes when pressing ESC key
- Modal title appears in header (not body)
- Only ONE header appears (no duplicates)
- Form validation works
- Action buttons work
- Loading states work
- Error states work
Modal Component Source
For reference, the global Modal component is at:
TypeScript: apps/frontend/src/app/components/modals/modal/modal.ts
- Inputs:
open,title,closeOnBackdropClick,closeOnEsc - Outputs:
closeModal - Methods:
onBackdropClick(),onEscapeKey(),onCloseClick(),onTabKey()
Template: apps/frontend/src/app/components/modals/modal/modal.html
- Overlay with backdrop click handler
- Header with title and close button
- Body with
<ng-content>for default content - Footer with
<ng-content select="[modal-actions]">for action buttons
Styles: apps/frontend/src/app/components/modals/modal/modal.css
- Animations, layout, accessibility
Quick Checklist
Before creating a modal, verify:
- Import
Modalcomponent - Use
(closeModal)output (NOTcloseRequested, NOTclose) - Use
titleinput (NOT slots) - Accept
openinput from parent - Emit
closeRequestedto parent - Create
handleClose()method to bridge events - Use
modal-actionsattribute for footer buttons - Create Storybook story
- Test all close methods in Storybook (X, backdrop, ESC)
- Verify no duplicate headers
Summary
The golden rule: Study existing working modals (add-child-modal, invite-modal, task-form-modal) and copy their patterns exactly. Don't invent new APIs or patterns.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?