Nib provides native macOS file dialogs (NSOpenPanel and NSSavePanel) through the FilePicker class. You can pick files, pick directories, and save files -- all with full access to dialog options like file type filtering, hidden file visibility, and Finder tag support.

Creating a FilePicker

import nib

picker = nib.FilePicker()

The FilePicker automatically uses the current running app instance. You can also pass an explicit App reference:

picker = nib.FilePicker(app)

Note

All FilePicker methods are blocking -- they pause execution until the user selects a file or cancels the dialog, then return the result directly. No callbacks needed.

Picking files

Use pick_files() to open a file selection dialog:

files = picker.pick_files(
    extensions=["txt", "md"],
    title="Select Text Files",
)

if files:
    for f in files:
        print(f"Name: {f.name}")
        print(f"Path: {f.path}")
        print(f"Size: {f.size} bytes")

The method returns a list of PickedFile objects, or None if the user cancelled.

Selecting multiple files

files = picker.pick_files(
    extensions=["png", "jpg", "gif"],
    multiple=True,
    title="Select Images",
)

if files:
    print(f"Selected {len(files)} file(s)")

Full parameter list

Parameter Type Default Description
multiple bool False Allow selecting multiple files
extensions list[str] None Allowed file extensions (e.g., ["png", "jpg"])
uttypes list[str] None Allowed Uniform Type Identifiers (e.g., ["public.image"])
directory str None Initial directory path
title str "Select Files" Dialog window title
message str None Prompt text shown below the title
button_label str "Open" Text for the OK button
shows_hidden_files bool False Show hidden files in the dialog
resolves_aliases bool True Follow alias files to their targets
allows_other_file_types bool False Allow files outside the allowed types
treats_packages_as_directories bool False Treat .app bundles as folders
validator Callable None Validation function (see below)

PickedFile dataclass

Each selected file is returned as a PickedFile with these fields:

Field Type Description
name str Filename (e.g., "document.txt")
path str Absolute file path
size int File size in bytes
uti str or None Uniform Type Identifier (e.g., "public.plain-text")
tags list[str] macOS Finder tags

Picking a directory

Use pick_directory() to let the user choose a folder:

dirs = picker.pick_directory(title="Select Output Folder")

if dirs:
    output_dir = dirs[0]
    print(f"Selected: {output_dir}")

The method returns a list of directory path strings, or None if cancelled.

Multiple directories

dirs = picker.pick_directory(
    multiple=True,
    title="Select Folders to Scan",
)

Full parameter list

Parameter Type Default Description
multiple bool False Allow selecting multiple directories
directory str None Initial directory path
title str "Select Folder" Dialog window title
message str None Prompt text shown below the title
button_label str "Select" Text for the OK button
shows_hidden_files bool False Show hidden files
resolves_aliases bool True Follow aliases to targets
can_create_directories bool True Allow creating new folders
validator Callable None Validation function

Saving a file

Use save_file() to show a save dialog:

result = picker.save_file(
    filename="output.txt",
    extensions=["txt"],
    title="Save Report",
)

if result:
    print(f"Save to: {result.path}")
    print(f"Tags: {result.tags}")

The method returns a SaveResult object, or None if cancelled.

Full parameter list

Parameter Type Default Description
filename str None Suggested filename
extensions list[str] None Allowed file extensions
uttypes list[str] None Allowed Uniform Type Identifiers
directory str None Initial directory path
title str "Save File" Dialog window title
message str None Prompt text shown below the title
button_label str "Save" Text for the Save button
name_field_label str "Save As:" Label for the filename field
shows_hidden_files bool False Show hidden files
can_create_directories bool True Allow creating new folders
allows_other_file_types bool False Allow extensions outside the allowed list
shows_tag_field bool True Show the Finder tags selector
validator Callable None Validation function

SaveResult dataclass

Field Type Description
path str The chosen save path
tags list[str] User-selected Finder tags

Validation

All three methods accept a validator parameter. The validator is a function that receives the selected path(s) and returns None if valid, or an error message string if invalid:

def validate_size(paths: list[str]) -> str | None:
    import os
    for path in paths:
        if os.path.getsize(path) > 10_000_000:
            return "File must be under 10 MB"
    return None

files = picker.pick_files(
    extensions=["csv"],
    validator=validate_size,
)

For save_file(), the validator receives a single path string instead of a list:

def validate_save(path: str) -> str | None:
    if path.endswith(".system"):
        return "Cannot overwrite system files"
    return None

result = picker.save_file(validator=validate_save)

App-level shortcuts

The App class also provides shortcut methods for file dialogs that use a callback-based pattern:

def main(app: nib.App):
    def handle_file(path):
        print(f"Selected: {path}")

    def handle_save(path):
        print(f"Save to: {path}")

    app.build(
        nib.VStack(
            controls=[
                nib.Button("Open File", action=lambda: app.open_file_dialog(
                    callback=handle_file,
                    types=["txt", "md"],
                )),
                nib.Button("Save File", action=lambda: app.save_file_dialog(
                    callback=handle_save,
                )),
            ],
            spacing=8,
            padding=16,
        )
    )

Tip

Prefer the FilePicker class for new code. It provides a more complete API with blocking semantics that are easier to work with.

Complete example

A file viewer app that lets you open text files and display their contents:

import nib


def main(app: nib.App):
    app.title = "File Viewer"
    app.icon = nib.SFSymbol("doc.text")
    app.width = 400
    app.height = 500

    picker = nib.FilePicker()

    file_name = nib.Text("No file selected", font=nib.Font.HEADLINE)
    file_content = nib.Text("", font=nib.Font.system(12))
    file_size = nib.Text("", foreground_color=nib.Color.GRAY)

    def open_file():
        files = picker.pick_files(
            extensions=["txt", "md", "py", "json"],
            title="Open Text File",
            message="Choose a file to view",
        )
        if files:
            f = files[0]
            file_name.content = f.name
            file_size.content = f"{f.size:,} bytes"
            try:
                with open(f.path, "r") as fh:
                    file_content.content = fh.read()
            except Exception as e:
                file_content.content = f"Error reading file: {e}"

    def save_file():
        if not file_content.content:
            return
        result = picker.save_file(
            filename="copy.txt",
            extensions=["txt"],
            title="Save Copy",
        )
        if result:
            with open(result.path, "w") as fh:
                fh.write(file_content.content)
            app.notify("Saved", f"File saved to {result.path}")

    app.build(
        nib.VStack(
            controls=[
                nib.HStack(
                    controls=[
                        file_name,
                        nib.Spacer(),
                        file_size,
                    ],
                ),
                nib.Divider(),
                nib.ScrollView(
                    controls=[file_content],
                ),
                nib.HStack(
                    controls=[
                        nib.Button("Open", action=open_file),
                        nib.Button("Save Copy", action=save_file),
                    ],
                    spacing=8,
                ),
            ],
            spacing=8,
            padding=16,
        )
    )


nib.run(main)