⚡ Rapidly scaffold production‑ready Clojure web apps with a professional, themed UI.
A quick glimpse of the generated UI (login, dashboards, grids, subgrids).
📸 Screenshots • 🎬 Demo • 🚀 Quick Start • ✨ Features • 🆕 What's New • Use this template → • Latest release • Changelog
Like this? ⭐ Star and 🔱 Fork to support development.
Prefer local? See Quick Start below.
Once published, you can generate a new app directly from the registry:
lein new org.clojars.hector/lst myapp
For local development of this template (contributing here), use the steps in Quick Start to lein install
and then run lein new lst myapp
.
Pick one of the zero-setup options and get running in under a minute:
lein new lst gpapp && cd gpapp && lein with-profile dev run
lein new lst csapp && cd csapp && lein with-profile dev run
ciapp/
or create your own and run:lein with-profile dev run
Then open http://localhost:3000
LST is a professional Leiningen template for building full‑stack, database‑backed Clojure web applications in minutes—not weeks. It generates complete CRUD grids, dashboards, and reports with a clean MVC structure, Bootstrap 5 themes, DataTables, built‑in auth, and multi‑database support.
Elevator pitch:
Below are example screenshots of LST-generated interfaces. These images demonstrate the look and feel of CRUD grids, dashboards, reports, subgrids, authentication, and other UI components as generated by the template.
Screenshot | Description |
---|---|
![]() | Public Page |
![]() | Login Form |
![]() | Login Form |
![]() | Change Password Form |
![]() | Private Page |
![]() | Contactos Dashboard |
![]() | Reports Menu |
![]() | Users Report |
![]() | Administration Menu |
![]() | Contactos Grid |
![]() | Administration Menu |
![]() | Siblings Subgrid |
![]() | Subgrid Edit Record |
![]() | Subgrid New Record |
![]() | Grid Edit with thumbnail |
![]() | Grid Edit with image |
![]() | Grid showing thumbnails |
All screenshots are from a default LST application using Bootstrap 5 styling. You can fully customize the appearance and layout to match your branding.
Below is an auto-generated GIF that gives a quick tour of the login, dashboard, grids, subgrids, and image fields.
If the GIF is missing, run the workflow below or the local script to create it.
Tip: Click the image to view it full size.
Two ways to build docs/demo.gif
from the repository screenshots:
docs/demo.gif
back to main
.convert
tool).scripts/make-demo-gif.sh 3 # default 3 fps; increase for faster playback
Tip: Try 4–5 fps for medium speed, 8–10 fps for fast.
Notes:
screenshot-*.png
files in a clear narrative order.project.clj
namespaces (use dotted {{name}}.core
, {{name}}.dev
) so parsing succeeds; verified tests passciapp/
before generation; ignore ciapp/
in gitSee all releases and notes: https://github.com/hectorqlucero/lst/releases
Feature | Description | Benefit |
---|---|---|
Rapid Scaffolding | Generate complete CRUD in seconds | 🚀 10x faster development |
Database Agnostic | MySQL, PostgreSQL, SQLite support | 🔄 Easy deployment flexibility |
Modern UI | Bootstrap 5 + DataTables | 💎 Professional appearance |
Security Built-in | Role-based access control | 🔒 Enterprise-ready security |
Modal Subgrids | Master-detail relationships | 📊 Rich data relationships |
Migration System | Database versioning | 🔧 Easy schema management |
Requirement | Version | Download |
---|---|---|
Java | 17+ | OpenJDK |
Clojure | 1.10+ | Clojure.org |
Leiningen | 2.9.0+ | Leiningen.org |
# 1. Clone and install the template
git clone <your-repo-url>
cd lst
lein clean && lein deps && lein install
# 2. Create your new application
lein new lst myapp
cd myapp
# 3. Configure database (see Configuration section)
# 4. Run migrations and seed data
lein migrate
lein database
# 5. Start the development server
lein with-profile dev run
🎉 Success! Open http://localhost:3000
Edit resources/private/config.clj
with your database credentials:
{:connections
{ ;; --- Mysql database ---
:mysql {:db-type "mysql" ;; "mysql", "postgresql", "sqlite", etc.
:db-class "com.mysql.cj.jdbc.Driver" ;; JDBC driver class
:db-name "//localhost:3306/your_dbname" ;; JDBC subname (host:port/db)
:db-user "root"
:db-pwd "your_password"}
;; --- Local SQLite database ---
:sqlite {:db-type "sqlite"
:db-class "org.sqlite.JDBC"
:db-name "db/your_dbname.sqlite"} ;; No user/pwd needed for SQLite
;; --- PostgreSQL database ---
:postgres {:db-type "postgresql"
:db-class "org.postgresql.Driver"
:db-name "//localhost:5432/your_dbname"
:db-user "root"
:db-pwd "your_password"}
;; --- Default connection used by the app ---
:main :postgres ; Used for migrations
:default :postgres ; Used for generators (lein grid, lein dashboard, etc.)
:db :mysql
:pg :postgres
:localdb :sqlite}
;; --- Other global app settings ---
:uploads "./uploads/your_upload_folder/" ;; Path for file uploads
:site-name "your_site_name" ;; App/site name
:company-name "your_company_name" ;; Company name
:port 3000 ;; App port
:tz "US/Pacific" ;; Timezone
:base-url "http://0.0.0.0:3000/" ;; Base URL
:img-url "https://0.0.0.0/uploads/" ;; Image base URL
:path "/uploads/" ;; Uploads path (for web)
:max-upload-mb 5
:allowed-image-exts ["jpg" "jpeg" "png" "gif" "bmp" "webp"]
;; Optional email config
:email-host "smtp.example.com"
:email-user "user@example.com"
:email-pwd "emailpassword"}
⚠️ Important: Always update
:main
and:default
to point to your primary database connection. This allows you to runlein migrate
,lein grid
,lein dashboard
, etc. without specifying the database each time. If you only use one database, set both to the same connection key (e.g.,:mysql
,:postgres
, or:sqlite
).
Section | Purpose | Required |
---|---|---|
:connections | Database connections and defaults | ✅ Yes |
:uploads | File upload directory path | ⚠️ If using file uploads |
:site-name | Application/site name | ⚠️ Recommended |
:company-name | Company name for branding | ⚠️ Recommended |
:port | Application server port | ⚠️ Recommended |
:tz | Application timezone | ⚠️ Recommended |
:base-url | Base URL for the application | ⚠️ Recommended |
:img-url | Base URL for images | ⚠️ If serving images |
:path | Web path for uploads | ⚠️ If using file uploads |
:email-* | SMTP email configuration | ⚠️ If sending emails |
Database | Setup Command | Migration Command |
---|---|---|
MySQL | CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; | lein migrate db |
PostgreSQL | CREATE DATABASE mydb; | lein migrate pg |
SQLite | Auto-created | lein migrate localdb |
After running lein database
, these test users are available:
Password | Role | Access Level | |
---|---|---|---|
user@example.com | user | User | Basic access |
admin@example.com | admin | Admin | Administrative access |
system@example.com | system | System | Full system access |
LST includes multiple Bootstrap 5 themes and a built‑in theme switcher in the navbar. Set a default theme in config.clj
, then change it anytime from the dropdown.
Edit resources/private/config.clj
in your generated app and set :theme
to one of the supported values:
;; resources/private/config.clj (excerpt)
{:theme "cerulean" ;; Options: "default" (Bootstrap), "cerulean", "slate", "minty", "lux", "cyborg", "sandstone", "superhero", "flatly", "yeti"
;; ...other settings...
}
The selected theme is applied via a theme-<name>
class on <body>
and corresponding styles are loaded automatically by the layout.
Use the Theme dropdown in the top navbar to switch instantly—no reload required. The default is whatever you set in :theme
.
Below are thumbnails of the bundled themes available in this template:
Theme | Preview |
---|---|
Default (Bootstrap) | ![]() |
Cerulean | ![]() |
Cyborg | ![]() |
Flatly | ![]() |
Lux | ![]() |
Minty | ![]() |
Sandstone | ![]() |
Slate | ![]() |
Superhero | ![]() |
Yeti | ![]() |
Notes:
:theme
, the app falls back to default
.myapp/
├── src/myapp/
│ ├── handlers/ # Application logic (MVC controllers)
│ │ ├── admin/ # Admin CRUD interfaces (one folder per table)
│ │ ├── reports/ # Custom report handlers
│ │ └── <table>/ # Dashboard/read-only handlers (one per table)
│ ├── models/ # Database models and seed data
│ │ └── cdb.clj # Seed/test data definitions
│ ├── routes/
│ │ ├── proutes.clj # Private (authenticated) routes
│ │ └── routes.clj # Public (open) routes
│ ├── layout.clj # Global page layout and shared UI
│ ├── menu.clj # Navigation and sidebar menu definitions
│ └── builder/ # Generator source templates (advanced customization)
└── resources/
├── private/
│ └── config.clj # Application and database configuration
└── migrations/ # Database migration SQL scripts
LST uses the Model-View-Controller (MVC) pattern to organize your application into clear, modular components:
Each grid, dashboard, or report generated by LST gets its own controller, model, and view.
This modular approach allows you to:
This structure ensures a clear separation of concerns, making your application scalable, maintainable, and highly flexible for future growth.
Purpose: Creates full Create, Read, Update, Delete interfaces
By default, all fields are inferred from the database schema.
Specifying fields is optional—only use it to customize labels or limit fields.
# Basic usage (all fields auto-detected)
lein grid users
# With database connection
lein grid pg customers
# With access restrictions
lein grid orders :rights [A S]
# (Optional) Specify fields and labels
lein grid products "Product Name:name" "Price:price" "Category:category_id"
Generated Files:
src/myapp/handlers/admin/<table>/controller.clj
src/myapp/handlers/admin/<table>/model.clj
src/myapp/handlers/admin/<table>/view.clj
Purpose: Creates read-only data tables
By default, all fields are inferred from the database schema.
Specifying fields is optional.
# Basic usage (all fields auto-detected)
lein dashboard products
# With access restrictions
lein dashboard users :rights [S]
# (Optional) Specify fields and labels
lein dashboard users "Name:firstname" "Email:email" "Joined:created_at"
Generated Files:
src/myapp/handlers/<table>/controller.clj
src/myapp/handlers/<table>/model.clj
src/myapp/handlers/<table>/view.clj
Purpose: Creates custom report pages
By default, all fields are inferred from the database schema.
Specifying fields is optional.
# Basic usage (all fields auto-detected)
lein report monthlySales
# With access restrictions
lein report userActivity :rights [A S]
# With database connection
lein report pg inventoryReport :set-default
Generated Files:
src/myapp/handlers/reports/<name>/controller.clj
src/myapp/handlers/reports/<name>/model.clj
src/myapp/handlers/reports/<name>/view.clj
Purpose: Creates child grids linked to parent records
By default, all fields are inferred from the database schema.
Specifying fields is optional.
# Basic usage (all fields auto-detected)
lein subgrid order_items orders order_id
# With access restrictions
lein subgrid user_addresses users user_id :rights [A]
# (Optional) Specify fields and labels
lein subgrid order_items orders order_id "Product:product_name" "Qty:quantity"
Requirements:
id
id
Example: A subgrid showing sibling items within an contactos.
Database | Driver | Connection Key | Production Ready |
---|---|---|---|
MySQL | com.mysql.cj.jdbc.Driver | db | ✅ Yes |
PostgreSQL | org.postgresql.Driver | pg | ✅ Yes |
SQLite | org.sqlite.JDBC | localdb | ⚠️ Development |
Command | Purpose | Example |
---|---|---|
lein migrate [conn] | Run pending migrations | lein migrate pg |
lein rollback [conn] | Rollback last migration | lein rollback |
lein database [conn] | Seed test data | lein database localdb |
Migrations are database-specific and located in resources/migrations/
:
resources/migrations/
├── 001-initial.mysql.up.sql
├── 001-initial.postgresql.up.sql
├── 001-initial.sqlite.up.sql
├── 002-users.mysql.up.sql
└── ...
Each code block should be copied into a file with the name shown above it.
001-parent.postgresql.down.sql
DROP TABLE IF EXISTS parent;
001-parent.postgresql.up.sql
CREATE TABLE IF NOT EXISTS parent (
id SERIAL PRIMARY KEY,
name TEXT,
created_on DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
event_time TIME,
is_active BOOLEAN DEFAULT FALSE,
amount NUMERIC(10,2),
note TEXT
);
002-child.postgresql.down.sql
DROP TABLE IF EXISTS child;
002-child.postgresql.up.sql
CREATE TABLE IF NOT EXISTS child (
id SERIAL PRIMARY KEY,
parent_id INTEGER,
name TEXT,
created_on DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
event_time TIME,
is_active BOOLEAN DEFAULT FALSE,
amount NUMERIC(10,2),
note TEXT,
FOREIGN KEY (parent_id) REFERENCES parent(id)
);
001-parent.mysql.down.sql
DROP TABLE IF EXISTS parent;
001-parent.mysql.up.sql
CREATE TABLE IF NOT EXISTS parent (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
created_on DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
event_time TIME,
is_active BOOLEAN DEFAULT FALSE,
amount DECIMAL(10,2),
note TEXT
)
ENGINE=InnoDB;
002-child.mysql.down.sql
DROP TABLE IF EXISTS child;
002-child.mysql.up.sql
CREATE TABLE IF NOT EXISTS child (
id INT AUTO_INCREMENT PRIMARY KEY,
parent_id INT,
name VARCHAR(100),
created_on DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
event_time TIME,
is_active BOOLEAN DEFAULT FALSE,
amount DECIMAL(10,2),
note TEXT,
FOREIGN KEY (parent_id) REFERENCES parent(id)
)
ENGINE=InnoDB;
001-parent.sqlite.down.sql
DROP TABLE IF EXISTS parent;
001-parent.sqlite.up.sql
CREATE TABLE IF NOT EXISTS parent (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
created_on TEXT,
created_at TEXT DEFAULT (datetime('now')),
event_time TEXT,
is_active INTEGER DEFAULT 0,
amount NUMERIC(10,2),
note TEXT
);
002-child.sqlite.down.sql
DROP TABLE IF EXISTS child;
002-child.sqlite.up.sql
CREATE TABLE IF NOT EXISTS child (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_id INTEGER,
name TEXT,
created_on TEXT,
created_at TEXT DEFAULT (datetime('now')),
event_time TEXT,
is_active INTEGER DEFAULT 0,
amount NUMERIC(10,2),
note TEXT,
FOREIGN KEY (parent_id) REFERENCES parent(id)
);
LST includes a focused test suite to validate routing helpers and database vendor abstractions. Tests are generated with every new app and run with the standard Leiningen workflow.
core_test.clj
(namespace: myapp.core-test
)
db_test.clj
(namespace: myapp.db-test
)
db_vendor_test.clj
(namespace: myapp.db-vendor-test
)
last-insert-id
behavior (MySQL/SQLite implemented, PostgreSQL returns nil
).All tests use in-memory stubs and do not require a live database connection.
From your generated app directory (e.g., myapp/
):
# Run all tests
lein test
# Run a specific namespace
lein test myapp.core-test
lein test myapp.db-test
lein test myapp.db-vendor-test
# Run a single test var
lein test :only myapp.db-test/time-projection-test
lein test :only myapp.db-vendor-test/last-insert-id-tests
Notes:
myapp
with your application’s actual namespace root.resources/private/config.clj
settings.Because this repository is a Leiningen template, the test sources live under the template path and are materialized into a generated project. To exercise them:
# 1) Install the template locally
lein clean && lein deps && lein install
# 2) Generate a fresh app from the template
lein new lst myapp
cd myapp
# 3) Run the test suite
lein test
Optional tips:
lein with-profile test test
.# Admin and System only
lein grid sensitive_data "Data:value" :rights [A S]
# System only
lein dashboard system_logs "Time:timestamp" "Message:message" :rights [S]
# All authenticated users (default)
lein dashboard public_info "Title:title" "Content:content"
Generated controllers automatically check user permissions:
(defn handler [request]
(if (auth/has-rights? (:session request) [:A :S])
(render-page request)
(response/redirect "/unauthorized")))
After generating a subgrid, integrate it into the parent view:
(ns myapp.handlers.admin.users.view
(:require [myapp.models.grid :refer [build-grid-with-subgrids create-subgrid-config]]))
(defn users-view [title rows]
(let [labels ["Name" "Email"]
db-fields [:firstname :email]
fields (apply array-map (interleave db-fields labels))
table-id "users_table"
href "/admin/users"
args {:new true :edit true :delete true
:subgrids [{:title "User Contacts"
:table-name "user_contacts"
:foreign-key "user_id"
:href "/admin/usercontactsusers"
:icon "bi bi-people"
:label "Contacts"}]}]
(build-grid-with-subgrids title rows table-id fields href args)))
A quick reference for where to find and update everything in your generated LST app.
Feature/Area | Location(s) | Purpose / What to Edit |
---|---|---|
CRUD Grids | src/myapp/handlers/admin/<table>/ | Controllers, models, and views for admin CRUD |
Dashboards | src/myapp/handlers/<table>/ | Read-only table controllers, models, and views |
Reports | src/myapp/handlers/reports/<name>/ | Custom report controllers, models, and views |
Subgrids | src/myapp/handlers/admin/<child>/ src/myapp/handlers/admin/<parent>/ | Child grid logic and parent integration |
Menus | src/myapp/menu.clj | Update navigation and sidebar links |
App Layout | src/myapp/layout.clj | Change global page layout and shared UI |
Routing | src/myapp/routes/ | URL routing for all features |
Config | resources/private/config.clj | Database and global app settings |
Seed Data | src/myapp/models/cdb.clj | Add/edit initial data for lein database |
Migrations | resources/migrations/ | SQL files for schema changes |
Builder Templates | src/myapp/builder/ | Source templates for code generators (advanced) |
src/myapp/menu.clj
.resources/private/config.clj
.src/myapp/models/cdb.clj
.src/myapp/builder/
.LST makes it easy to display images in your grid forms. To add an image field to a grid or form view, simply use the build-image-field
utility.
(require '[yourapp.models.form :refer [build-image-field]])
Replace yourapp.models.form
with the appropriate namespace for your grid.
build-image-field
in your view:(build-image-field row)
Here, row
is the map representing the current record. This will render the image field in your grid or form.
Example integration in a view:
(defn user-view [row]
[:div
[:h3 (:name row)]
(build-image-field row)])
This approach works for any grid or form where you want to display an image associated with a record.
LST provides a powerful form builder (form.clj
) that lets you create professional, accessible forms using all standard HTML5 field types. Each field is rendered with Bootstrap 5 styling and supports HTML5 validation, accessibility, and modern browser features.
build-field
function to define each form field.text
, email
, password
, number
, date
, datetime-local
, month
, week
, time
, color
, range
, file
, tel
, url
, search
, hidden
, select
, radio
, checkbox
, and textarea
.required
, pattern
, min
, max
, step
, autocomplete
, autofocus
, readonly
, disabled
, maxlength
, minlength
, and more are fully supported.Reference: The default form view template is defined in
src/myapp/builder/view.clj
and is used by all generated grids, dashboards, and reports. You can customize this template for advanced layouts or branding.
(form "/submit"
[(build-field {:label "Full Name" :type "text" :id "name" :name "name" :placeholder "Enter name" :required true :maxlength 50})
(build-field {:label "Email" :type "email" :id "email" :name "email" :placeholder "Enter email" :required true :autocomplete "email"})
(build-field {:label "Password" :type "password" :id "pw" :name "pw" :placeholder "Password" :required true :minlength 8})
(build-field {:label "Birthday" :type "date" :id "bday" :name "bday"})
(build-field {:label "User Level" :type "select" :id "level" :name "level" :value "U" :required true
:options [{:value "" :label "Select..."}
{:value "U" :label "User"}
{:value "A" :label "Admin"}
{:value "S" :label "Sys"}]})]
(list (build-primary-input-button {:type "submit" :value "Submit"})
(build-secondary-input-button {:type "button" :value "Cancel"}))
"Example Form")
Type | Description | Example Attribute(s) |
---|---|---|
text | Single-line text | :maxlength , :pattern |
email | Email address | :autocomplete "email" |
password | Password input | :minlength , :autocomplete |
number | Numeric input | :min , :max , :step |
date | Date picker | |
datetime-local | Local date and time | |
month | Month picker | |
week | Week picker | |
time | Time picker | |
color | Color picker | |
range | Slider input | :min , :max , :step |
file | File upload | :accept , :multiple |
tel | Telephone number | :pattern |
url | URL input | |
search | Search box | |
hidden | Hidden field | |
select | Dropdown/select | :options |
radio | Radio buttons | :options |
checkbox | Checkbox group | :options |
textarea | Multi-line text | :rows , :maxlength |
util.clj
)The src/myapp/models/util.clj
file provides many helper functions to make building forms easier and more dynamic.
get-options
The (get-options ...)
function lets you dynamically generate options for <select>
, radio, or checkbox fields directly from your database.
Signature:
(get-options table value-field label-fields & {:keys [sort-by filter-field filter-value]})
Parameters:
table
: Table name (string)value-field
: Field to use as the option value (string)label-fields
: Field(s) to use as the option label (string or vector of strings):sort-by
: (optional) Field(s) to sort by:filter-field
/ :filter-value
: (optional) Filter options by field/valueExample:
;; Get user options for a select field, showing "firstname lastname"
(get-options "users" "id" ["firstname" "lastname"] :sort-by "lastname")
;; => ({:value 1 :label "Alice Smith"} {:value 2 :label "Bob Jones"} ...)
;; Use in a form field:
(build-field {:label "User" :type "select" :name "user_id"
:options (get-options "users" "id" ["firstname" "lastname"] :sort-by "lastname")})
year-options
, month-options
: Generate year/month options from a table's date field.foreign-key
: Fetch a label from a related table for display.not-empty-str
, parse-int
, parse-float
, parse-bool
: Data cleaning and parsing helpers.slugify
: Create URL-friendly slugs from strings.See src/myapp/models/util.clj
for more details and examples.
Problem | Cause | Solution |
---|---|---|
Generated components not showing | Server needs to reload routes | Open src/myapp/core.clj , save it (no changes needed), then refresh webpage |
Routes not updating | Server cache | Restart dev server with lein with-profile dev run |
Database connection error | Wrong credentials | Check resources/private/config.clj |
Subgrid not opening | Missing foreign key | Verify FK exists in child table |
Permission denied | Wrong user role | Check :rights setting in generator |
Migration failed | SQL syntax error | Check database-specific SQL files |
# Check database connection
lein repl
=> (require '[myapp.db :as db])
=> (db/test-connection :db)
# Verify routes
=> (require '[myapp.routes.proutes :as routes])
=> (routes/app-routes)
# Test authentication
=> (require '[myapp.auth :as auth])
=> (auth/has-rights? {:user {:role "A"}} [:A :S])
lein grid <table> "Label:field" ... # Basic CRUD
lein grid pg <table> "Label:field" ... :set-default # PostgreSQL + save default
lein grid <table> "Label:field" ... :rights [A S] # Restricted access
lein dashboard <table> "Label:field" ... # Read-only table
lein dashboard <table> "Label:field" ... :rights [S] # System access only
lein report <name> # Basic report
lein report <name> :rights [A S] # Admin/System only
lein subgrid <child> <parent> <fk> "Label:field" ... # Master-detail
lein subgrid <child> <parent> <fk> :rights [A] # Admin only
Command | Description |
---|---|
lein migrate [conn] | Apply pending migrations |
lein rollback [conn] | Rollback last migration |
lein database [conn] | Seed test data |
Key | Database | Usage |
---|---|---|
db | MySQL | Default production |
pg | PostgreSQL | Alternative production |
localdb | SQLite | Development/testing |
This project is licensed under the MIT License - see the LICENSE file for details.
Publish a release to improve discoverability and trust (enables the Latest Release badge):
# Tag and push your first release
git tag v0.1.0
git push origin v0.1.0
This triggers the “Release on tag” workflow which creates a GitHub Release with auto-generated notes.
See all release notes at https://github.com/hectorqlucero/lst/releases
Generated with ❤️ by LST - Lucero Systems Template
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
Ctrl+k | Jump to recent docs |
← | Move to previous article |
→ | Move to next article |
Ctrl+/ | Jump to the search field |