Skip to content

Native Dialogs

This content is not available in your language yet.

OS 네이티브 modal — 파일 선택, 저장, 메시지 박스, 에러 popup. Electron dialog.* API와 호환되는 응답 형식.

APImacOSLinuxWindows
showMessageBox✓ NSAlert✓ GTK MessageDialog✓ TaskDialogIndirect, MessageBoxW fallback
showErrorBox✓ NSAlert (critical)✓ GTK MessageDialog(error)✓ MessageBoxW
showOpenDialog✓ NSOpenPanel✓ GTK FileChooserDialog✓ GetOpenFileNameW / IFileOpenDialog(folder)
showSaveDialog✓ NSSavePanel✓ GTK FileChooserDialog✓ GetSaveFileNameW

Linux는 GTK3 GtkMessageDialog / GtkFileChooserDialog를 사용한다. Windows는 Win32 native dialog를 사용한다. 단, parent window에 attach되는 sheet modal은 macOS 전용이며 Linux/Windows dialog는 free-floating modal로 뜬다.

두 가지 modal 모드 지원 (Electron 매칭):

첫 인자에 windowId 전달하면 부모 창 타이틀바에서 슬라이드 다운하는 sheet:

import { dialog, windows } from '@suji/api';
const { windowId } = await windows.create({ title: "Editor" });
const r = await dialog.showMessageBox(windowId, {
message: "저장하시겠습니까?",
buttons: ["저장", "취소"],
});

다른 창 입력은 가능 — sheet은 attach된 부모 창만 차단. macOS HIG 표준 UX. Linux/Windows에서는 windowId 인자를 받아도 native sheet로 attach하지 않고 일반 dialog 경로를 사용한다.

macOS는 ObjC block completion handler와 nested NSApp event loop로 동기 대기해 창 입력을 차단하지 않는다.

windowId 안 넘기면 free-floating runModal (앱 전체 입력 차단):

const r = await dialog.showMessageBox({ message: "..." });

빠르고 간단. 단일 창 앱이나 system-wide alert에 적합.

모든 함수는 Electron 두-인자 오버로드를 지원한다. 첫 인자로 windowId: number를 넘기면 macOS에서는 sheet, Windows/Linux에서는 free-floating 경로를 사용한다. 옵션 객체만 넘기면 free-floating.

import { dialog } from '@suji/api';
// Async (권장)
dialog.showMessageBox(options): Promise<{ response, checkboxChecked }>;
dialog.showMessageBox(windowId, options): Promise<...>; // macOS sheet
dialog.showErrorBox(title: string, content: string): Promise<void>;
dialog.showOpenDialog(options?): Promise<{ canceled, filePaths }>;
dialog.showOpenDialog(windowId, options?): Promise<...>; // macOS sheet
dialog.showSaveDialog(options?): Promise<{ canceled, filePath }>;
dialog.showSaveDialog(windowId, options?): Promise<...>; // macOS sheet
// Sync 변종 — 응답 shape만 다름 (raw 값 반환). 두-인자 오버로드 동일 지원.
dialog.showMessageBoxSync(options): Promise<number>;
dialog.showMessageBoxSync(windowId, options): Promise<number>;
dialog.showOpenDialogSync(options?): Promise<string[] | undefined>;
dialog.showOpenDialogSync(windowId, options?): Promise<string[] | undefined>;
dialog.showSaveDialogSync(options?): Promise<string | undefined>;
dialog.showSaveDialogSync(windowId, options?): Promise<string | undefined>;

알림 / 확인 / 경고 dialog.

const { response, checkboxChecked } = await dialog.showMessageBox({
type: 'warning', // 'none' | 'info' | 'warning' | 'error' | 'question'
title: '저장되지 않은 변경사항', // 창 타이틀
message: '변경사항을 저장하시겠습니까?',
detail: '저장 안 하면 변경 내용이 사라집니다.', // 보조 텍스트 (작은 폰트)
buttons: ['저장', '저장 안 함', '취소'],
defaultId: 0, // Enter로 활성화될 버튼 (생략 시 첫 번째)
cancelId: 2, // ESC로 활성화될 버튼
checkboxLabel: '다음에 다시 묻지 않기', // suppression checkbox (생략 가능)
checkboxChecked: false, // 체크박스 초기 상태
});
if (response === 0) {
// 저장 클릭
}
typeNSAlertStyle아이콘
'info'NSAlertStyleInformational(없음)
'warning'NSAlertStyleWarning⚠️
'error'NSAlertStyleCritical
'question'NSAlertStyleWarning⚠️ (Electron과 동일 — macOS는 question 별도 스타일 없음)
'none' (기본)NSAlertStyleWarning⚠️

Windows는 가능한 경우 TaskDialogIndirect를 런타임 해상도(LoadLibraryW + GetProcAddress)로 호출한다. SxS manifest나 OS 상태 때문에 사용할 수 없으면 MessageBoxW로 fallback한다. TaskDialog path는 커스텀 버튼 라벨을 보존하지만, fallback path는 Win32 표준 버튼 조합(OK, OK/Cancel, Yes/No, Yes/No/Cancel)으로 축약된다.

Linux는 GtkMessageDialog를 사용한다. 버튼은 custom response id로 추가해 Electron식 button index를 보존하고, checkboxLabel은 message area에 GtkCheckButton을 붙여 checkboxChecked로 되돌린다.

단순 에러 popup — 단일 OK 버튼, critical 스타일.

await dialog.showErrorBox('저장 실패', '디스크 공간이 부족합니다.');

showMessageBox({ type: 'error', message: content, title, buttons: ['OK'] })와 동일.

파일/폴더 선택.

const { canceled, filePaths } = await dialog.showOpenDialog({
title: '파일 선택',
defaultPath: '~/Documents', // 초기 디렉토리
buttonLabel: '선택', // "Open" 대체
message: '여러 파일을 동시에 선택할 수 있습니다.', // 다이얼로그 상단 메시지 (macOS 한정)
filters: [
{ name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'gif'] },
{ name: 'Documents', extensions: ['pdf', 'txt', 'md'] },
],
properties: ['openFile', 'multiSelections'],
});
if (!canceled) {
for (const path of filePaths) {
console.log('선택됨:', path);
}
}

문자열 union 배열 — Electron과 동일.

Property동작
'openFile'파일 선택 가능 (기본 ON)
'openDirectory'폴더 선택 가능
'multiSelections'여러 항목 선택 가능
'showHiddenFiles'. 시작하는 hidden 파일 표시
'createDirectory'”New Folder” 버튼 활성
'noResolveAliases'symbolic link / alias를 따라가지 않고 그 자체 반환
'treatPackageAsDirectory'.app/.pkg 같은 bundle을 폴더로 표시

{ name, extensions } 배열. macOS UI는 필터 그룹 dropdown을 더 이상 노출하지 않아 (Big Sur부터) 모든 그룹의 extensions가 통합되어 허용name 필드는 호환성용이며 화면에 표시되지 않음. Linux는 GtkFileFilter, Windows는 OPENFILENAMEW.lpstrFilter 형식으로 필터 그룹을 전달한다. 확장자는 점 없이.

extensions: ['*']이거나 빈 배열이면 모든 파일 허용.

  • 슬래시로 끝나거나 (/path/to/dir/) 기존 디렉토리면 → 초기 디렉토리만 설정.
  • 그 외에는 (directory, filename)으로 분리해 디렉토리 + 파일명 입력란 미리 채움.
defaultPath: '/Users/me/Documents/', // → 폴더만
defaultPath: '/Users/me/Documents/draft.md', // → 폴더 + 파일명 'draft.md'
defaultPath: 'output.txt', // → 파일명만 (slash 없음)

저장 경로 선택.

const { canceled, filePath } = await dialog.showSaveDialog({
title: '저장',
defaultPath: '~/Documents/output.pdf',
buttonLabel: '내보내기',
nameFieldLabel: '파일명:',
filters: [{ name: 'PDF', extensions: ['pdf'] }],
});
if (!canceled) {
await fs.writeFile(filePath, data);
}
Property동작
'showHiddenFiles'hidden 파일 표시
'createDirectory'”New Folder” 버튼 활성
'treatPackageAsDirectory'bundle을 폴더로

NSSavePanel은 기본적으로 overwrite confirmation을 사용자가 끄지 못하게 함 — Electron의 'showOverwriteConfirmation' 옵션은 macOS에서 항상 ON 상태로 무시됨. Linux GTK와 Windows Win32 path는 overwrite confirmation 옵션을 native dialog에 전달한다. Windows는 showOverwriteConfirmation이 true일 때 OFN_OVERWRITEPROMPT를 사용한다.

macOS Finder의 태그 입력 필드를 dialog 하단에 표시 (NSSavePanel 한정). 사용자가 입력한 태그는 저장 시 파일에 자동 적용. 우리 응답에는 별도 필드로 노출 안 함 — Electron도 동일.

await dialog.showSaveDialog({ showsTagField: true });
// 취소 시
{ canceled: true, filePaths: [] } // showOpenDialog
{ canceled: true, filePath: '' } // showSaveDialog
// MessageBox는 항상 buttons 중 하나 선택
{ response: 0, checkboxChecked: false }

이 응답 형식은 Electron과 동일.

옵션 타입 불일치 (예: defaultId: "string")는 std.json parse 단계에서 실패 → 즉시 graceful 응답:

// showMessageBox parse fail
{ response: 0, checkboxChecked: false, error: 'parse' }
// showOpenDialog parse fail
{ canceled: true, filePaths: [], error: 'parse' }

런타임 type-safe하지 않은 코드(예: TypeScript 미사용)도 crash 없이 동작.

Zig/Rust/Go/Node 4개 백엔드 SDK 모두 typed wrapper를 노출하며, frontend @suji/api와 동일한 응답 형식을 사용한다.

// Zig
const r = suji.dialog.messageBoxSimple("info", "안녕", &.{ "OK", "Cancel" });
const r = suji.dialog.showOpenDialog("\"properties\":[\"openFile\"]");
suji.dialog.showErrorBox("Error", "Something failed");
// Rust
use suji::dialog::{self, MessageBoxOpts};
let r = dialog::show_message_box(MessageBoxOpts {
message: "저장하시겠습니까?",
buttons: vec!["저장", "취소"],
window_id: Some(1), // sheet on window 1
..Default::default()
});
let r = dialog::show_open_dialog(r#""properties":["openFile"]"#);
dialog::show_error_box("Error", "Something failed");
// Go
import "github.com/ohah/suji-go/dialog"
r := dialog.ShowMessageBox(dialog.MessageBoxOpts{
Message: "저장하시겠습니까?",
Buttons: []string{"저장", "취소"},
WindowID: 1, // sheet
})
r := dialog.ShowOpenDialog(`"properties":["openFile"]`)
dialog.ShowErrorBox("Error", "Something failed")
// Node
import { dialog } from '@suji/node';
const r = await dialog.showMessageBox({
message: "저장하시겠습니까?",
buttons: ["저장", "취소"],
windowId: 1,
});
const r = await dialog.showOpenDialog({ properties: ["openFile"] });
await dialog.showErrorBox("Error", "Something failed");

응답 형식은 모든 SDK 동일 — frontend @suji/api와 매칭.

Electron 대비 아직 노출하지 않거나 플랫폼별 native backend에서 무시되는 옵션들.

누락 항목Electron API현재 동작비고
Sheet modalfree-floating only✅ 구현됨 (windowId 첫 인자)
bookmarks / bookmark 반환 필드macOS sandbox security-scoped응답에서 누락securityScopedBookmarks: true일 때만 — 샌드박스 앱 한정
securityScopedBookmarks 옵션OpenDialog/SaveDialog무시샌드박스 한정
icon: NativeImageMessageBox 커스텀 아이콘NSAlert 기본 아이콘만
textWidth: numberMessageBox 메시지 폭macOS 기본
type: 'none' 정확 매칭macOS는 아이콘 없음NSAlertStyleWarning(경고 아이콘)NSAlert는 항상 app icon overlay
type: 'question' 정확 매칭macOS는 question 아이콘NSAlertStyleWarningmacOS NSAlert에 question 스타일 없음 (Electron도 동일 매핑)
showCertificateTrustDialog인증서 신뢰 dialog미구현
Windows 전용 옵션 (promptToCreate / dontAddToRecent / noLink / normalizeAccessKeys)Windows 한정일부 미반영
  • Sheet modal은 macOS 전용: Linux/Windows는 parent window attach 없이 free-floating dialog를 사용한다.
  • buttons 제한: macOS NSAlert path는 16개, Windows TaskDialog path는 8개까지 커스텀 버튼을 보존한다.
  • Dialog 텍스트 4KB 제한: 메시지가 4KB를 초과하면 잘린다.
  • filters name UI 미표시: macOS Big Sur+에서 NSOpenPanel은 필터 그룹 dropdown 미노출 — 모든 extensions가 통합 허용.
  • Windows 파일 dialog 옵션 일부 제한: buttonLabel, message, showsTagField, treatPackageAsDirectory, alias 처리 같은 macOS 전용 옵션은 Win32 dialog에서 무시된다.
  • Linux 파일 dialog 옵션 일부 제한: message, nameFieldLabel, showsTagField, treatPackageAsDirectory, alias 처리 같은 macOS 전용 옵션은 GTK dialog에서 무시된다.

잘못된 옵션을 넘겨도 crash 없이 graceful parse error 응답이 반환된다. 실제 dialog의 시각적 동작 확인은 examples/dialogs-demo를 통한 수동 검증을 권장한다.