모달 Modal
모달은 대화창의 한 종류로 기본 창에 종속된 요소이다. 기본 창과 겹쳐져 가장 상단에 표시되며, 이때 기본 창은 비활성 상태로 전환되어 상호작용이 불가능하므로 사용자는 모달에서의 단일한 과업 또는 메시지에 집중할 수 있다.
기본
[(open)]으로 열림 상태를 제어하고, 대화 상자의 이름이 되는 제목은 heading으로 줍니다. 아래쪽 버튼은 <div krdsModalFooter>로 감싸 넣습니다. 모달은 다른 요소의 쌓임 순서에 가리지 않도록 body 끝으로 옮겨 그립니다.
열리면 모달 안으로 초점이 이동하고 Tab 키 초점이 모달 안에서 순환하며, 배경 페이지는 스크롤되지 않습니다. Esc 키, 배경 클릭, 닫기(X) 버튼으로 닫을 수 있고, 닫히면 모달을 연 버튼으로 초점이 돌아갑니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-basic',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<button krdsButton size="large" variant="secondary" (click)="open.set(true)">모달 열기</button>
<krds-modal heading="모달 제목" [(open)]="open">
<p>대화 상자는 사용자에게 작업에 대해 알리고 중요한 정보를 포함하거나 결정이 필요하거나 여러 작업을 포함할 수 있습니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" variant="tertiary" (click)="open.set(false)">아니요</button>
<button krdsButton size="medium" (click)="open.set(false)">예</button>
</div>
</krds-modal>
`
})
export default class ModalBasic {
readonly open = signal(false)
}사이즈
size로 small, medium(기본), large 중 하나를 고릅니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter, type KrdsModalSize } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-sizes',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<div style="display: flex; flex-wrap: wrap; gap: 1rem">
@for (option of sizes; track option.size) {
<button krdsButton size="large" variant="secondary" (click)="show(option.size)">{{ option.label }} 모달 열기</button>
}
</div>
<krds-modal [heading]="current().label + ' 모달'" [size]="current().size" [(open)]="open">
<p>{{ current().text }} 크기의 모달입니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" (click)="open.set(false)">확인</button>
</div>
</krds-modal>
`
})
export default class ModalSizes {
readonly sizes: { size: KrdsModalSize; label: string; text: string }[] = [
{ size: 'small', label: 'Small', text: '작은' },
{ size: 'medium', label: 'Medium', text: '기본' },
{ size: 'large', label: 'Large', text: '큰' }
]
readonly current = signal(this.sizes[0])
readonly open = signal(false)
show(size: KrdsModalSize) {
this.current.set(this.sizes.find(option => option.size === size)!)
this.open.set(true)
}
}배경 없음
[backdrop]="false"면 배경을 어둡게 하지 않습니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-no-backdrop',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<button krdsButton size="large" variant="secondary" (click)="open.set(true)">배경 없는 모달 열기</button>
<krds-modal heading="배경 없는 모달" [backdrop]="false" [(open)]="open">
<p>배경 딤 처리 없이 모달만 표시됩니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" (click)="open.set(false)">확인</button>
</div>
</krds-modal>
`
})
export default class ModalNoBackdrop {
readonly open = signal(false)
}풀팝업
full을 주면 화면 전체를 덮는 풀팝업으로 표시됩니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-full',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<button krdsButton size="large" variant="secondary" (click)="open.set(true)">풀팝업 열기</button>
<krds-modal heading="풀팝업" full [(open)]="open">
<p>전체 화면을 차지하는 풀팝업입니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" (click)="open.set(false)">확인</button>
</div>
</krds-modal>
`
})
export default class ModalFull {
readonly open = signal(false)
}바텀시트
bottomSheet를 주면 화면 아래에서 올라오는 바텀시트로 표시됩니다. KRDS는 바텀시트를 모바일 화면에서 쓰도록 안내합니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-bottom-sheet',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<button krdsButton size="large" variant="secondary" (click)="open.set(true)">바텀시트 열기</button>
<krds-modal heading="바텀시트" bottomSheet [(open)]="open">
<p>하단에서 올라오는 바텀시트입니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" (click)="open.set(false)">확인</button>
</div>
</krds-modal>
`
})
export default class ModalBottomSheet {
readonly open = signal(false)
}Persistent
중요한 결정을 받아야 할 때 persistent를 주면 배경 클릭과 Esc 키로 닫히지 않고, 닫기(X) 버튼과 직접 둔 버튼으로만 닫힙니다.
ts
import { Component, signal } from '@angular/core'
import { KrdsButton, KrdsModal, KrdsModalFooter } from '@krds.ui/angular'
@Component({
selector: 'demo-modal-persistent',
imports: [KrdsButton, KrdsModal, KrdsModalFooter],
template: `
<button krdsButton size="large" variant="secondary" (click)="open.set(true)">Persistent 모달 열기</button>
<krds-modal heading="Persistent 모달" persistent [(open)]="open">
<p>배경을 클릭하거나 Esc 키를 눌러도 닫히지 않습니다. 버튼으로만 닫을 수 있습니다.</p>
<div krdsModalFooter>
<button krdsButton size="medium" (click)="open.set(false)">확인</button>
</div>
</krds-modal>
`
})
export default class ModalPersistent {
readonly open = signal(false)
}KrdsModal
선택자 krds-modal
Inputs
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
[(open)] | boolean | false | 열림 여부 |
heading (필수) | string | - | 제목 (대화 상자의 이름) |
modalId | string | - | 대화 상자 id (없으면 자동 생성) |
backdrop | boolean | true | 배경을 어둡게 |
size | 'small' | 'medium' | 'large' | 'medium' | 크기 |
full | boolean | false | 화면 전체 |
bottomSheet | boolean | false | 바텀시트 (모바일 아래에서 올라옴) |
persistent | boolean | false | Esc·배경 클릭으로 닫지 않음 |
KrdsModalFooter
선택자 [krdsModalFooter]
Inputs
없음