DEV Community

Cover image for Chandra: Cross-Platform Desktop GUIs in Perl
LNATION for LNATION

Posted on

Chandra: Cross-Platform Desktop GUIs in Perl

Building desktop applications has traditionally been a challenge in the Perl ecosystem. While we have excellent tools for web development, backend services, and system administration, creating native feeling desktop apps often meant learning and wrestling with complex GUI toolkits or abandoning Perl altogether.

Chandra attempts to change that equation.

Chat

What is Chandra?

Chandra is a Perl module that lets you build cross-platform desktop applications using the web technologies you already know — HTML, CSS, and JavaScript — while keeping your application logic in Perl. Under the hood, it leverages native webview components (WebKit on macOS/Linux, Edge on Windows) to render your UI, giving you the best of both worlds: native performance with web flexibility.

use Chandra::App;

my $app = Chandra::App->new(
    title  => 'My First App',
    width  => 800,
    height => 600,
);

$app->set_content('<h1>Hello from Perl!</h1>');
$app->run;
Enter fullscreen mode Exit fullscreen mode

That's it. Five lines to create a windowed application.

Hello World

The Bridge Between Worlds

The real power of Chandra lies in its seamless bidirectional communication between Perl and JavaScript. You can expose Perl functions to your frontend with a simple bind call:

my $app = Chandra::App->new(title => 'Counter');

my $count = 0;

$app->bind('increment', sub {
    $count++;
    return $count;
});

$app->bind('decrement', sub {
    $count--;
    return $count;
});

$app->set_content(<<'HTML');
<!DOCTYPE html>
<html>
<body>
    <h1 id="counter">0</h1>
    <button onclick="inc()">+</button>
    <button onclick="dec()">-</button>
    <script>
        async function inc() {
            let val = await window.chandra.invoke('increment');
            document.getElementById('counter').textContent = val;
        }
        // or
        function dec() {
            window.chandra.invoke('decrement').then(function(val) {
                document.getElementById('counter').textContent = val;
            });
        }
    </script>
</body>
</html>
HTML

$app->run;
Enter fullscreen mode Exit fullscreen mode

Your JavaScript calls window.chandra.invoke('increment'), Perl increments the counter, and the result flows back as a Promise. JSON serialization happens automatically.

Increment

Building UIs the Perl Way

If you prefer constructing your UI in Perl rather than writing raw HTML strings, Chandra::Element provides a DOM-like interface:

use Chandra::Element;

my $ui = Chandra::Element->new({
    tag => 'div',
    class => 'container',
    style => { padding => '20px', background => '#f5f5f5' },
    children => [
        { tag => 'h1', data => 'Welcome' },
        {
            tag => 'button',
            data => 'Click Me',
            onclick => sub {
                my ($event, $app) = @_;
                print "Button clicked!\n";
            },
        },
    ],
});

$app->set_content($ui);
Enter fullscreen mode Exit fullscreen mode

Event handlers are automatically wired up — when the button is clicked, your Perl callback fires.

Single-Page Apps with Built-in Routing

Modern desktop apps often benefit from SPA-style navigation. Chandra has you covered:

my $app = Chandra::App->new(title => 'My SPA');

$app->layout(sub {
    my ($body) = @_;
    return qq{
        <nav>
            <a href="/">Home</a>
            <a href="/settings">Settings</a>
        </nav>
        <div id="chandra-content">$body</div>
    };
});

$app->route('/' => sub {
    return '<h1>Home</h1><p>Welcome!</p>';
});

$app->route('/settings' => sub {
    return '<h1>Settings</h1><p>Configure your app...</p>';
});

$app->route('/user/:id' => sub {
    my (%params) = @_;
    return "<h1>User $params{id}</h1>";
});

$app->run;
Enter fullscreen mode Exit fullscreen mode

Navigation happens client-side with no page reloads — just like a web SPA, but in a native window.

Native Integration Done Right

Chandra doesn't just wrap a browser. It provides genuine desktop integration:

Native Dialogs

# File picker
my $path = $app->dialog->open_file(title => 'Select a file');

# Directory picker  
my $dir = $app->dialog->open_directory;

# Save dialog
my $save_path = $app->dialog->save_file(
    title   => 'Save As',
    default => 'document.txt',
);

# Alert dialogs
$app->dialog->info(message => 'Operation complete!');
$app->dialog->error(message => 'Something went wrong');
Enter fullscreen mode Exit fullscreen mode

Dialog

System Tray

use Chandra::Tray;

my $tray = Chandra::Tray->new(
    app     => $app,
    icon    => 'icon.png',
    tooltip => 'My App',
);

$tray->add_item('Show Window' => sub { $app->show });
$tray->add_separator;
$tray->add_item('Quit' => sub { $app->terminate });
$tray->show;
Enter fullscreen mode Exit fullscreen mode

Tray

Persistent Storage

my $store = $app->store;  # ~/.chandra/<app-name>/store.json

$store->set('theme', 'dark');
$store->set('window.width', 1200);
$store->set('recent_files', ['/path/a', '/path/b']);

my $theme = $store->get('theme');  # 'dark'
Enter fullscreen mode Exit fullscreen mode

Dot notation for nested keys, automatic JSON serialization, atomic writes with file locking — storage that just works.

Hot Reload

Wtch your source files and refresh automatically:

$app->watch('lib/', sub {
    my ($changed) = @_;
    print "Files changed: @$changed\n";
    $app->set_content(build_ui());
    $app->refresh;
});

$app->run;
Enter fullscreen mode Exit fullscreen mode

Reload

DevTools

Need to inspect elements or debug JavaScript? Enable developer tools:

my $app = Chandra::App->new(
    title => 'Debug App',
    debug => 1,  # Press F12 to open DevTools
);
Enter fullscreen mode Exit fullscreen mode

Platform Support

Chandra works across the major desktop platforms:

  • macOS — Uses WebKit via WKWebView
  • Linux — Uses WebKitGTK
  • Windows — Uses MSHTML/Edge

Getting Started

Install from CPAN:

cpanm Chandra
Enter fullscreen mode Exit fullscreen mode

Or from source:

perl Makefile.PL
make
make install
Enter fullscreen mode Exit fullscreen mode

Then dive into the examples directory for working demonstrations of all major features or I also have several modules already available for you to test:

Van Goph

Tetris

A Side note: I'm currently on look out for a new contract or permanent opportunity. If you know of any relevant openings, I'd appreciate hearing from you - email@lnation.org

Top comments (0)