Szablony paczek

Cztery paczki do pobrania i przerobienia na własne. Każda przechodzi nkb lint bez błędu i bez ostrzeżenia, a każda pokazuje inną część formatu.

Jak użyć szablonu?

Pobierz go do własnego folderu, a paleta od razu go pokaże. W palecie naciśnij AltShiftSpace , kliknij Pack folders, potem Create and open, żeby otworzyć ten folder. Potem przerób szablon na własną paczkę: nadaj mu własne id, zmień tak samo nazwę pliku i zastąp wartości swoimi. Własna paczka objaśnia każdy wiersz.

Każdy szablon działa od razu, więc można wypróbować go w palecie, zanim cokolwiek się w nim zmieni. Szablony są po angielsku, a własną paczkę można pisać po polsku.

Pierwsza paczka

Trzy wartości dla pola z nazwiskiem: spacja na końcu, apostrof i pusta wartość. Najkrótsza paczka na start, z komentarzem przy wierszach, które są ważne za pierwszym razem.

Pobierz first-pack.toml

# Your first pack: three values for a name field. Replace them with your own.
#
# A pack is this one file. Put it in your own folder: in the palette, open
# the value window, click Pack folders, then Create and open. Check it with
# `nkb lint first-pack.toml`, which names every problem and how to fix it.
#
# The guide that explains every line: https://naughtykeyboard.donislawdev.com/docs/write-a-pack/
#
# A template from https://naughtykeyboard.donislawdev.com/docs/pack-templates/
# Change anything in it. When you change `id`, rename the file to match,
# first-pack.toml now, because a pack is found by its file name.

format = 1

[pack]
id          = "first-pack"
name        = "First pack"
description = "Three values for a name field, to show how a pack is written."
version     = "1.0"
updated     = 2026-11-10
license     = "CC-BY-4.0"
authors     = ["Naughty Keyboard"]
language    = "en"
tags        = ["example", "names"]
fields      = ["name", "text"]
risk        = "normal"

# Each value is one [[values]] block. The palette types them in this order,
# so put the value most likely to find a bug first. A space at the end is
# written as \u0020, because an editor can drop one written out.
[[values]]
id     = "trailing-space"
name   = "Name with a space at the end"
value  = "Kowalski\u0020"
breaks = "The sign-up form keeps the space and the login form trims it, or the other way round, so the account is created and then cannot be found."
expect = "Trimmed everywhere or kept everywhere, so the same name always finds the same account."
since  = "1.0"

[[values]]
id     = "apostrophe"
name   = "Name with an apostrophe"
value  = "O'Brien"
breaks = "Code that builds a query or a script by pasting text into it reads the apostrophe as the end of a string, so the save fails with a raw error or the name is cut short."
expect = "Saved and shown as O'Brien, with no error."
since  = "1.0"

[[values]]
id     = "empty"
name   = "Empty value"
value  = ""
breaks = "The browser treats an empty field as missing and the server as an empty string, or the other way round, so a required field is saved empty."
expect = "One clear answer on both sides: refused with a message, or saved as empty on purpose."
since  = "1.0"

Pole z limitem długości

Wartości na granicy limitu 50 znaków: jeden znak poniżej, dokładnie limit i jeden znak ponad, a do tego ta sama długość w polskich literach, w emoji, w spacjach i w literach z osobnym akcentem, które inaczej liczą się w bajtach, w punktach kodowych i w jednostkach, które liczy przeglądarka. Zmień 50 na limit swojego pola, w każdym count i w każdej nazwie. Pokazuje, jak type = "repeat" zapisuje długą wartość w kilku wierszach.

Pobierz text-field-limit.toml

# A field with a limit of 50 characters. Change 50 to the limit of your field
# in every `count` and every name below, and the pack tests that limit.
#
# A value this long is written as a short piece and a count, with
# type = "repeat", so the file stays readable.
#
# A template from https://naughtykeyboard.donislawdev.com/docs/pack-templates/
# Change anything in it. When you change `id`, rename the file to match,
# text-field-limit.toml now, because a pack is found by its file name.

format = 1

[pack]
id          = "text-field-limit"
name        = "Text field limit"
description = "Values at the edge of a 50-character limit, counted in letters, in bytes and in the units a browser counts."
version     = "1.0"
updated     = 2026-11-10
license     = "CC-BY-4.0"
authors     = ["Naughty Keyboard"]
language    = "en"
tags        = ["length", "limit", "boundary"]
fields      = ["text", "name"]
risk        = "normal"

[[values]]
id     = "len-51"
name   = "51 characters, one over the limit"
type   = "repeat"
unit   = "a"
count  = 51
breaks = "A limit checked only in the browser lets it through when the request is sent another way, and a database column of 50 cuts the last letter or refuses the row with a raw error."
expect = "Refused with a message that names the limit, and never saved cut short."
since  = "1.0"

[[values]]
id     = "len-50"
name   = "50 characters, exactly the limit"
type   = "repeat"
unit   = "a"
count  = 50
breaks = "A check written as less than instead of at most refuses a value the field promises to take, so the longest allowed text is the one that fails."
expect = "Accepted and saved whole."
since  = "1.0"

[[values]]
id     = "polish-letters-50"
name   = "50 Polish letters, 100 bytes"
type   = "repeat"
unit   = "ą"
count  = 50
breaks = "Each of these letters takes two bytes in UTF-8, so a column or an API with a limit in bytes refuses or cuts a value the form counted as 50 characters."
expect = "Accepted whole, or refused with the same limit the form shows."
since  = "1.0"

[[values]]
id     = "emoji-50"
name   = "50 emoji"
type   = "repeat"
unit   = "😀"
count  = 50
breaks = "JavaScript counts length in UTF-16 units, two for each of these, so the form sees 100 where the server may see 50, and a cut in the middle of one leaves a broken character."
expect = "The browser and the server count the same way, and nothing is cut in half."
since  = "1.0"

[[values]]
id     = "spaces-51"
name   = "51 spaces"
type   = "repeat"
unit   = "\u0020"
count  = 51
breaks = "Over the limit before trimming and empty after it, so the answer depends on which check runs first, and a required field can be saved empty."
expect = "Refused as empty, with the same answer whichever check runs first."
since  = "1.0"

[[values]]
id     = "combining-50"
name   = "50 letters with a separate accent"
type   = "repeat"
unit   = "e\u0301"
count  = 50
breaks = "Each letter is two code points, an e and an accent, so a counter in code points sees 100 where a reader sees 50 letters, and a cut can drop the accent from the last one."
expect = "Counted as 50 letters, or normalised on save and counted the same way everywhere."
since  = "1.0"

[[values]]
id     = "len-49"
name   = "49 characters, one under the limit"
type   = "repeat"
unit   = "a"
count  = 49
breaks = "The control just under the limit. If it is refused too, the limit is smaller than the form says, and every other result here needs reading again."
expect = "Accepted and saved whole."
since  = "1.0"

Formularz rejestracji

Pole na adres e-mail z drugim polem, które go powtarza, i login, który ma być unikalny. Pokazuje dwie pary: ten sam adres ze spacją na końcu i bez niej oraz dwa loginy, które wyglądają tak samo, a są zapisane w dwóch alfabetach, drugi z cyrylicką literą zapisaną jako sekwencja ucieczki.

Pobierz sign-up-form.toml

# A sign-up form: an e-mail field with a second field that repeats it, and a
# login that has to be unique. Two pairs join values that are typed one after
# the other: the first half into one field, the second half into the next.
#
# A template from https://naughtykeyboard.donislawdev.com/docs/pack-templates/
# Change anything in it. When you change `id`, rename the file to match,
# sign-up-form.toml now, because a pack is found by its file name.

format = 1

[pack]
id          = "sign-up-form"
name        = "Sign-up form"
description = "Values for an e-mail field, the field that repeats it, and a login that has to be unique."
version     = "1.0"
updated     = 2026-11-10
license     = "CC-BY-4.0"
authors     = ["Naughty Keyboard"]
language    = "en"
tags        = ["sign-up", "email", "login"]
fields      = ["email", "username"]
risk        = "normal"

[[values]]
id     = "email"
name   = "E-mail address"
value  = "jan.kowalski@example.com"
breaks = "The first half of a pair, and on its own a plain address every form takes. Type it into the e-mail field, then the next value into the field that repeats it."
expect = "Accepted."
since  = "1.0"

[[values]]
id     = "email-trailing-space"
name   = "The same address with a space at the end"
value  = "jan.kowalski@example.com\u0020"
breaks = "The second half of the pair. A form that trims one field and not the other says the two addresses differ, or saves the address with the space and no mail arrives."
expect = "Trimmed in both fields, and accepted as the same address."
since  = "1.0"

[[values]]
id     = "login-latin"
name   = "Login in Latin letters"
value  = "kowalski"
breaks = "The first half of a pair, and on its own an ordinary login. Sign up with it, then try to sign up again with the next value."
expect = "Accepted."
since  = "1.0"

[[values]]
id     = "login-cyrillic-o"
name   = "The same login with a Cyrillic o"
value  = "k\u043Ewalski"
breaks = "The second half. It looks exactly like the first login, but one letter is Cyrillic, so a uniqueness check that compares bytes makes a second account nobody can tell from the first."
expect = "Refused as taken, or accepted and shown so the difference can be seen."
since  = "1.0"

[[values]]
id     = "email-plus-tag"
name   = "Address with a plus sign"
value  = "jan.kowalski+shop@example.com"
breaks = "A check that allows only letters, digits and dots refuses a valid address many people use to sort their mail, and they cannot sign up at all."
expect = "Accepted, and the confirmation mail arrives."
since  = "1.0"

[[pairs]]
id       = "repeat-email"
name     = "E-mail and the field that repeats it"
relation = "identity"
a        = "email"
b        = "email-trailing-space"
breaks   = "The two fields are compared before they are trimmed, so a person who typed the same address twice is told the addresses differ."
expect   = "Both fields trimmed the same way, and the two accepted as one address."
since    = "1.0"

[[pairs]]
id       = "look-alike-login"
name     = "Two logins that look the same"
relation = "look-alike"
a        = "login-latin"
b        = "login-cyrillic-o"
breaks   = "Two accounts with logins nobody can tell apart, so a message, a payment or an invitation goes to the wrong person."
expect   = "The second login refused as taken, or both kept and shown so they can be told apart."
since    = "1.0"

Daty rezerwacji

Zameldowanie o godzinie, którą zmiana czasu pomija albo powtarza, pobyt, który kończy się następnego ranka, i data daleko w przyszłości. Pokazuje type = "next-date", datę zapisaną jako reguła, więc nigdy nie przechodzi w przeszłość, i parę z początkiem i końcem.

Pobierz booking-dates.toml

# Booking dates: a check-in at an hour the clocks skip or repeat, a stay that
# ends the next morning, and a date far ahead. The two clock-change dates are
# written as a rule, type = "next-date", and worked out on the day they are
# typed, so they never fall into the past.
#
# A template from https://naughtykeyboard.donislawdev.com/docs/pack-templates/
# Change anything in it. When you change `id`, rename the file to match,
# booking-dates.toml now, because a pack is found by its file name.

format = 1

[pack]
id          = "booking-dates"
name        = "Booking dates"
description = "Dates and times for a booking form: hours the clocks skip or repeat, a stay past midnight and a date far ahead."
version     = "1.0"
updated     = 2026-11-10
license     = "CC-BY-4.0"
authors     = ["Naughty Keyboard"]
language    = "en"
tags        = ["dates", "time", "booking"]
fields      = ["datetime", "date", "time"]
risk        = "normal"

[[values]]
id      = "spring-forward"
name    = "Check-in at an hour the clocks skip"
type    = "next-date"
month   = 3
week    = "last"
weekday = "sunday"
time    = "02:30"
breaks  = "On the night clocks go forward in the European Union this hour does not exist, so a form moves it to 03:30 without saying so, or fails with a raw error."
expect  = "Refused with an explanation, or moved with the new time shown."
since   = "1.0"

[[values]]
id      = "fall-back"
name    = "Check-in at an hour the clocks repeat"
type    = "next-date"
month   = 10
week    = "last"
weekday = "sunday"
time    = "02:30"
breaks  = "On the night clocks go back in the European Union this hour happens twice, so a booking stored without a zone cannot say which of the two it means, and a stay of one hour can last two."
expect  = "The time stored with its zone or offset, or the form asks which of the two hours is meant."
since   = "1.0"

[[values]]
id     = "start-evening"
name   = "Start at six in the evening"
value  = "18:00"
breaks = "The first half of a pair, and on its own an ordinary start time. Type it into the start field, then the next value into the end field."
expect = "Accepted."
fields = ["time"]
since  = "1.0"

[[values]]
id     = "end-next-morning"
name   = "End at nine the next morning"
value  = "09:00"
breaks = "The second half: an end earlier than the start. A form that compares the two times alone refuses an overnight stay, or saves one that ends before it begins."
expect = "Read as the next morning and shown that way, or refused with a message that asks for the date."
fields = ["time"]
since  = "1.0"

[[values]]
id     = "far-future"
name   = "The last day of the year 9999"
value  = "9999-12-31"
breaks = "A date picker, a database column or a library with a narrower range refuses it with a raw error, cuts the year, or saves it and then sorts it before today."
expect = "Refused with a message that names the latest date allowed, or saved and shown as written."
fields = ["date"]
since  = "1.0"

[[pairs]]
id       = "overnight"
name     = "A stay that ends the next morning"
relation = "range"
a        = "start-evening"
b        = "end-next-morning"
breaks   = "A range check that compares times without dates reads the end as earlier than the start, so an overnight booking is refused or saved the wrong way round."
expect   = "Accepted as a stay into the next morning, with both dates shown."
since    = "1.0"