Skip to content

Native dialogs ​

Access through app.dialogs (CryKit::Dialogs::Manager). Native dialogs support macOS 11+ and run synchronously on the application's UI context.

Methods ​

MethodReturnsPurpose
open_file(title, filters = [], directory = nil)String?Select one file; nil on cancel
open_files(title, filters = [], directory = nil)Array(String)Select several files; empty on cancel
open_directory(title, directory = nil)String?Select a directory; nil on cancel
save_file(title, default_name = "", filters = [], directory = nil)String?Choose a save path; does not write a file
info(title, message)NilShow an information alert
warning(title, message)NilShow a warning alert
error(title, message)NilShow an error alert
confirm(title, message)BoolAsk Yes/No; Escape selects No
question(title, message, buttons, cancel:, default: nil)SymbolReturn a custom button's action ID

Arguments can be passed by name, as in app.dialogs.open_file(title: "Open"). confirm has no default Return action; Yes requires explicitly activating its button. Backend failures raise exceptions rather than returning cancellation.

crystal
if path = app.dialogs.open_file(title: "Open document")
  content = File.read(path)
end

if app.dialogs.confirm(title: "Delete draft", message: "Delete this draft?")
  delete_draft
end

These snippets assume application code running in a UI callback.

Calling context ​

Call siteSupported?
Mounted view event, menu callback, or tray callbackYes
job.notify, stream handler, or channel handlerYes, on the UI context
Window close or navigation approval callbackYes
Background worker or before native startupNo

The native backend raises CryKit::Dialogs::InvalidContextError outside the main UI thread or while the application is not running. Other platforms raise CryKit::Dialogs::UnsupportedError. Dialogs wait for the user's response; mounted event callbacks have no dialog response deadline.

Pass your application to views that need dialogs:

crystal
class FolderPicker
  include CryKit::View

  reactive path : String = "No directory selected"

  def initialize(@app : CryKit::Application)
  end

  private def choose_directory : Nil
    if path = @app.dialogs.open_directory(title: "Choose a directory")
      reactive.path = path
    end
  end

  template "folder_picker"
end

Save folder_picker.ecr beside the Crystal file and mount with mount FolderPicker.new(application):

ecr
<button on_click=<%~ -> { choose_directory } %>>Choose directory</button>
<p><%= reactive.path %></p>

Questions with custom buttons ​

crystal
action = app.dialogs.question(
  title: "Unsaved changes",
  message: "Save this document before closing?",
  buttons: [
    CryKit::Dialogs::Button.new(:save, "Save"),
    CryKit::Dialogs::Button.new(:discard, "Don't Save"),
    CryKit::Dialogs::Button.new(:cancel, "Cancel"),
  ],
  default: :save,
  cancel: :cancel)
InputRule
buttonsAt least one CryKit::Dialogs::Button; unique, nonempty Symbol IDs and nonblank labels
cancel:Required button ID; selected by Escape or native dismissal
default:Optional button ID selected by Return; must differ from cancel:
Title, message, labelsValid UTF-8 without null bytes
ResultA supplied button ID, independent of its label

Invalid requests raise ArgumentError. Omit default: when a decision should require an explicit choice. Button labels add no implicit keyboard shortcuts.

Injected backend contract

Implement question(request : CryKit::Dialogs::Question) : Symbol and return one of the request's IDs. Backends without question support inherit an UnsupportedError fallback. A response outside the supplied IDs raises.

Starting directory ​

All file pickers accept directory:. Use an absolute path for a specific initial location:

crystal
path = app.dialogs.save_file(
  title: "Export report",
  default_name: "report.csv",
  filters: [{name: "CSV files", extensions: ["csv"]}],
  directory: File.dirname(current_document_path))

Omitted, nil, or empty directory: leaves the location to macOS, including its location restoration. CryKit does not check whether that directory exists or is accessible; macOS handles unavailable locations.

File filters ​

InputBehavior
{name: "Images", extensions: ["png", "jpg"]}A CryKit::Dialogs::FileFilter
Multiple groupsmacOS allows the union of their extensions; group labels are not displayed
Empty groups or blank entriesIgnored; surrounding spaces are trimmed
No extensionsUnrestricted selection
Invalid extensionRaises before opening the panel

Extensions omit the leading dot and cannot contain path separators, wildcards, delimiters, internal whitespace, or control characters. macOS uses allowedContentTypes, including dynamic types for custom extensions.

Usage guidance ​

  • Treat cancellation as a normal result and leave application state unchanged.
  • Validate selected file contents and handle read/write failures separately.
  • Select paths on the UI context, then use background jobs for substantial import or export work.

Built with Crystal. Documented with VitePress.