SimplyDialogs
This is demonstration / test page for github.com/davidkonrad/simplydialogs. Latest release v1.1.0 (10.05.2025).
SimplyDialogs offers a range of common dialogs, all highly customizeable. Comes with no dependencies and a really small footprint.
The input dialog can be used to create rather large forms with validation,
see the Forms section.
Basic usage, assuming you have installed / included SimplyDialogs :
const Dlg = SimplyDialogs
Dlg.alert('Lorem ipsum ..')
Dlg.information('Lorem ipsum ..')
Dlg.bell('Lorem ipsum ..')
Dlg.error('Lorem ipsum ..')
Dlg.confirm('Lorem ipsum ..').then(answer => {
console.log(answer)
})
const wait = Dlg.wait('Lorem ipsum ..')
setTimeout(() => { wait.close() }, 1500)
Dlg.input('Lorem ipsum ..').then(input => {
console.log('input', input)
})
SimplyDialogs can be stacked upon each other endlessly
You can customise the dialog by passing an extra options argument
Dlg.confirm('Lorem ipsum ..', options).then(answer => {...})
options should follow the same structure as the DEFAULTS literal discussed below.
DEFAULTS
Simplydialogs uses internally a defaults literal, which is accessible through the .DEFAULTS property :
let defaults = {
return: true,
escape: true,
backdrop: undefined,
classes: '',
headers: {
alert: 'Alert',
error: 'Error',
confirm: 'Confirm',
information: 'Information',
bell: 'Notice',
input: ''
},
icons: {
alert: '⚠',
error: '⛔',
confirm: '✔️',
information: '💡',
bell: '🔔',
wait: '⚙️',
input: '✏️'
},
buttons: {
captions: {
ok: 'Ok',
cancel: 'Cancel',
yes: 'Yes',
no: 'No'
},
classes: {
ok: '',
cancel: '',
yes: '',
no: ''
}
},
progress: {
max: undefined,
value: undefined
},
input: {
formLayout: 'left full-width',
classes: {
label: '',
input: ''
},
inputs: [
{ type: 'input', inputType: 'text', label: 'Input ', name: 'input', placeholder: 'Enter text ..' },
],
callback: function(state) {
return state.input && state.input.length > 1
},
promise: undefined
}
}
You can alter those defaults by simple assignment, i.e <instance>.DEFAULTS.icons.alert = '<img src="path/to/image">' .
Changes to DEFAULTS are global, but you can still pass alternative options to each dialog (see later).
Any of the DEFAULTS settings can be overruled by passing an options literal as second parameter.
The structure of the options argument must follow the same format as DEFAULTS.
For convenience you can use the shorthand aliases header and icon instead of repeating the convoluted literal :
Dlg.alert('Lorem ipsum dolor sit amet ...', { icon: '🔥', header: 'Danger!' })
Captions, language-settings
There is no such thing as "language" in SimplyDialogs, but most people would like to adjust their basic dialogs to the lingo or language they use.
You can use DEFAULTS to change the dialogs into "spanish" as default by :
const Dlg = SimplyDialogs
Dlg.DEFAULTS.buttons.captions = {
ok: 'Ok',
yes: 'Aceptar',
no: 'Salir',
cancel: 'Cancelar'
}
Dlg.DEFAULTS.headers = {
alert: 'Alerta',
error: 'Error',
confirm: 'Confirmar',
information: 'Información',
bell: 'Nota',
input: 'Input'
}
//change placeholder for the default input dialog as well
Dlg.DEFAULTS.input.inputs[0].placeholder = 'Introducir texto ..'
If a button.caption is '', undefined or similar, the button will be hidden.
Icons
Icons can be replaced the same way; either individually or as a whole. For example, use another standard unicode emoji :
Dlg.DEFAULTS.icons.alert = '💥' //U+1F4A5, collision
See also the integration section. Here an example using Font Awesome icons :
Dlg.DEFAULTS.icons = {
alert: '<i class="fa fa-warning" style="color:fuchsia;"></i>',
information: '<i class="fa fa-info-circle" style="color:royalblue;"></i>',
confirm: '<i class="fa fa-question-circle" style="color:forestgreen;"></i>',
bell: '<i class="fa fa-bell" style="color:orange;"></i>',
error: '<i class="fa fa-times-circle-o" style="color:maroon;"></i>',
input: '<i class="fa fa-pencil" style="color:navy;"></i>',
wait: '<i class="fa fa-gear" style="color:gray;"></i>'
}
classes
You can specify custom classes for the <dialog element itself.
Either static through .DEFAULTS or on the fly via options :
Dlg.DEFAULTS.classes = 'my dialog classes'
Dlg.alert('Lorem ipsum ..', { classes: 'my dialog classes'}).then(answer =>
However, the main purpose with classes is to use predefined classes for specifying size and position for the dialog,
like top right medium which is discussed in separate sections below. A few additional builtin classes might
occasionally be useful :
no-padding | The dialog has no interior padding |
no-shadow | The dialog has no shadow |
no-backdrop | The dialog has no backdrop |
dark-backdrop | The dialog has a darker backdrop |
Sizing
You can use predefined class names to set dialog default size
Dlg.DEFAULTS.classes = 'small'
xs / xsmall | 18vw / 15vh | xs-width / xs-height |
sm / small | 22vw / 18vh | sm-width / sm-height |
md / medium | 34vw / 31vh | md-width / md-height |
lg / large | 55vw / 52vh | lg-width / lg-height |
fullsize | 100vw / 100vh | |
You can override the default using options
Dlg.info('some text ...', { classes: 'xs' }).then(..)
Note: The examples show dimensions, they are not meant to fit
Positioning
Per default all dialogs are shown in the center of the viewscreen, but vertically centered between middle and top.
Both the default centered position and position middle are leaning to the top for aesthetic reasons.
The default middle is 9vh from the top. The left, right margin is 4vw;
top, bottom has 5vh margin.
You can control the dialog position by using the predefined classes
top, middle, bottom>,
left, center, right
in any meaningful combination. Remember: center is vertical, middle is horizontal.
This snippet show all combinations stacked upon each other :
['top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right'].forEach(function(position) {
Dlg.info(position, { classes: position })
})
Keyboard
Specify how to react on keyboard strokes, return and escape.
You can set both true or false for all type of dialogs, except wait and progress,
but is mostly useful for the input-dialog. Note: Return / enter are ignored in textareas.
const options = {
return: (true | false),
escape: (true | false),
input: {
inputs: [
{ type: 'input', inputType: 'text', label: 'Input', name: 'input', required: true, minlength: 3 },
{ type: 'textarea', label: 'Textarea', name: 'textarea', spellcheck: false }
]
}
}
This produces a form where you need to enter at least 3 characters to the <input> field, and also have an optional <textarea>.
In the first sample you can hit enter and submit the form, in the second you'll need to click on OK (and so on).
progress
The progress dialog cannot be closed or cancelled by the user, but must be closed programmatically by using the close() method.
By default the <progress> element is shown in indeterminate state (ongoing with no indication)
const progress = Dlg.progress('Lorem ipsum ...')
setTimeout(() => progress.close(), 2000)
To make a progressive progress, specify a max value and use setValue to update the progress
const progress = Dlg.progress('Lorem ipsum ...', { progress: { value: 0, max: 100 }})
//mimick a progressive task
let interval, value = 0
const update = function() {
value++
progress.setValue(value)
progress.setText(`progress ${value}%`)
if (value > 100) {
clearInterval(interval)
progress.close()
}
}
interval = setInterval(update, 20)
wait
When using the wait-dialog, it can be convenient to update the dialog for details about progress or
whatever there is going on. wait has two special methods for updating the message, setText and addText.
Both takes a string as argument, that can be plain text or HTML.
setText
setText replaces the message with new content. Consider this mockup :
let counter = 0
let interval = undefined
const wait = Dlg.wait('Lorem ipsum ...')
const replace = function() {
counter ++
wait.setText(`text or message changed ${counter} time(s)`)
if (counter > 5) {
clearInterval(interval)
wait.close()
}
}
interval = setInterval(replace, 1000)
addText
addText appends something to the already existing text/message. The content can be plain text or HTML. Consider this mockup :
let counter = 0
let interval = undefined
const wait = Dlg.wait('Lorem ipsum ...')
const append = function() {
counter ++
wait.addText(`<br>${counter} line(s) added to message`)
if (counter > 5) {
clearInterval(interval)
wait.close()
}
}
interval = setInterval(append, 1000)
backdrop
Across the browsers, their computed default ::backdrop CSS more or less look like this :
dialog::backdrop {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background: rgba(0, 0, 0, 0.1);
}
You can change DEFAULTS.backdrop in order to change the background of the dialogs. You can specify the background style itself by passing a valid CSS class-definition. Here are some examples:
Dlg.DEFAULTS.backdrop = 'background: rgba(0, 0, 0, 0);'
Dlg.DEFAULTS.backdrop = 'background: rgba(0, 0, 0, 0.5);'
Or get more inspiration at https://projects.verou.me/css3patterns/, where the ideas below are taken from :
Dlg.DEFAULTS.backdrop = `background: linear-gradient(27deg, #151515 5px, transparent 5px) 0 5px,
linear-gradient(207deg, #151515 5px, transparent 5px) 10px 0px,
linear-gradient(27deg, #222 5px, transparent 5px) 0px 10px,
linear-gradient(207deg, #222 5px, transparent 5px) 10px 5px,
linear-gradient(90deg, #1b1b1b 10px, transparent 10px),
linear-gradient(#1d1d1d 25%, #1a1a1a 25%, #1a1a1a 50%, transparent 50%, transparent 75%, #242424 75%, #242424);
background-color: #131313;
opacity: 0.4;
background-size: 20px 20px;`
Dlg.DEFAULTS.backdrop = `background: linear-gradient(63deg, #999 23%, transparent 23%) 7px 0,
linear-gradient(63deg, transparent 74%, #999 78%),
linear-gradient(63deg, transparent 34%, #999 38%, #999 58%, transparent 62%), #444;
opacity: 0.4;
background-size: 16px 48px;`
Forms
SimplyDialogs are designed for handling advanced inputs (or forms). With built-in form validation, customiseable by
callbacks (or promises), and taking care of spellcheck and autocomplete, you can show rather complex forms just by a few lines of code.
First remember the input section of
the DEFAULTS literal :
const options = {
...
input: {
formLayout: '',
classes: {
label: '',
input: ''
},
inputs: [
{ .. },
{ .. }, //<- define form controls here
],
callback: undefined,
promise: undefined
}
}
Basically you show a form by passing a list to input.inputs, specifying which inputs/form controls
that should go into the form, along with their properties/settings.
Here is an example with a simple login form, demonstrating some of the basic features :
const options = {
header: 'Login',
icon: '🔑',
input: {
inputs: [{
type: 'input',
inputType: 'text',
label: 'Username',
name: 'username',
required: true,
minlength: 3,
//autocomplete: true,
//spellcheck: true,
//classes: 'required'
}, {
type: 'input',
inputType: 'password',
label: 'Password',
name: 'password',
required: true,
minlength: 3,
//autocomplete: true,
//classes: 'required'
}]
}
}
Dlg.input('', options).then(input => console.log('login', input))
Notice the use of required and minlength. The submit button is disabled until all required criterias are fulfilled.
Also notice the absence of browser-enforced spellcheck and autocomplete, those features are opt-in.
If the predefined required class is added to classes, the form control is outlined according to its validation status.
Form controls
Use the type property to specify the type of form control. You should also at least specify a name for each control,
if not the control is given an anonymous name like select_1.
const options = {
input: {
inputs: [
{
type: 'input' || 'textarea' || 'select' || 'radio',
name: 'name',
... //control specific properties
},
]
}
}
Each type is discussed below :
input
Set inputType to specify the type of the <input>.
See MDN's <input> types for a complete reference.
const options = {
input: {
formLayout: 'left clear-left',
inputs: [
{ type: 'input', inputType: 'text', label: 'Text', name: 'input', placeholder: 'Type some text ...' },
{ type: 'input', inputType: 'checkbox', label: 'Checkbox', name: 'checkbox', checked: true },
{ type: 'input', inputType: 'color', label: 'Color', name: 'color', value: '#123456' },
{ type: 'input', inputType: 'number', label: 'Number', name: 'number', value: 42 },
{ type: 'input', inputType: 'password', label: 'Password', name: 'password', style: 'color:maroon;' },
{ type: 'input', inputType: 'date', label: 'Date', name: 'date' },
{ type: 'input', inputType: 'datetime-local', label: 'Datetime-local', name: 'datetime-local' },
{ type: 'input', inputType: 'week', label: 'Week', name: 'week' },
{ type: 'input', inputType: 'file', label: 'File', name: 'file' },
{ type: 'input', inputType: 'url', label: 'Url', name: 'url' },
{ type: 'input', inputType: 'tel', label: 'Tel', name: 'tel' },
{ type: 'input', inputType: 'email', label: 'Email', name: 'email' },
{ type: 'input', inputType: 'range', label: 'Range', name: 'range', value: 25, max: 100 },
{ type: 'input', inputType: 'hidden', label: '', name: 'hidden', value: 'Pass extra values to the form via hidden inputs' }
]
}
}
Dlg.input('Lorem ipsum ...', options).then(input => console.log('result', input))
You can pass any <input>-specific attribute and it will blindly be rendered, even style.
input takes care of the following properties :
inputType |
as described above | |
value |
empty by default | |
classes |
CSS classes | important predefined classes are password, required |
required |
false by default | not supported by inputType color, range, hidden |
spellcheck |
false by default | only relevant for inputType text |
select
type: 'select produces a <select> control. Use options to define the selects' options :
const options = {
input: {
inputs [{
type: 'select', label: 'select', name: 'select', value: 'option2',
options: [
{ label: 'option1', value: 'option1' },
{ label: 'option2', value: 'option2' },
{ label: 'option3', value: 'option3' }
]
}
]
}
}
radio
type: radio renders a group of radio buttons.
Use options to define the radio group items :
const options = {
input: {
inputs: [{
type: 'radio', label: 'radio', name: 'radio', value: 'option2', //inline: true,
options: [
{ label: 'option1', value: 'option1' },
{ label: 'option2', value: 'option2' },
{ label: 'option3', value: 'option3' }
]
}
]
}
}
radio takes special care of the following properties :
options |
as described above | |
inline |
false by default | continuous rendering of the inputs |
classes |
CSS classes | |
required |
false by default | Will reset any value |
textarea
type: 'select produces a <select> control. Use options to define the selects' options :
const options = {
input: {
inputs [{
type: 'select', label: 'select', name: 'select', value: 'option2',
options: [
{ label: 'option1', value: 'option1' },
{ label: 'option2', value: 'option2' },
{ label: 'option3', value: 'option3' }
]
}
]
}
}
formLayout
A few classes that let you specify the layout of the form
left, top |
labels to the left or on top of form controls, default is left |
full-width |
form controls 100% width, default |
form-clear |
by default the form floats inline after icon and message (if set), form-left clears the form to the left |
const options = {
input: {
formLayout: 'top | left | full-width | form-clear',
inputs: [
{ type: 'input', inputType: 'text', label: 'Input', name: 'input', placeholder: 'Input required' },
{ type: 'textarea', label: 'Textarea', name: 'textarea', placeholder: 'Additional text', rows: 4 }
]
}
}
autofocus
If you set autofocus: true, then the input (or textarea, select)
will be focused when the dialog is shown
spellcheck
Browser spellcheck is disabled per default and therefore opt-in;
Use spellcheck: true to enable spellcheck
autocomplete
Built-in browser autocomplete is opt-in as well;
Use autocomplete: 'on' to enable autocomplete (input elements only)
Form validation
You can choose between 'native' declarative form validation by specifying form controls as required,
or programmatically by passing a callback, that can be either a function or a promise.
required
In your options, mark one or more form controls as required and specify their constraints (if needed),
just as you would have done in HTML markup. SimplyDialogs support the same limits and patterns you can build by using
required
and HTML5 properties. Here is a very basic example where the user is prompted to enter a valid email address :
const options = {
header: 'Enter valid email',
icon: '📧',
input: {
inputs: [{
type: 'input',
inputType: 'email',
name: 'email',
spellcheck: false,
required: true //<-- mark required as true
}],
}
}
Dlg.input('', options).then(input => {
console.log('email', input)
})
The "magic" works, you dont even have to specify some constraints, because an input of type email always will be invalid if the email address is invalid - thus the submit button is not enabled before a valid email address is entered. Here is another very basic example where we are asking for a danish-style phone number, 8 digits :
const options = {
header: 'Phone number',
icon: null,
input: {
inputs: [{
type: 'input',
inputType: 'text',
name: 'phone',
label: '+45',
required: true,
pattern: '[0-9]{8}'
}],
}
}
Dlg.input('Enter 8 digit number, regional code implied', options).then(input => {
console.log('phone', input)
})
btn-validation-required-danish-phonenumber
Mark any All you need to do is to mark the form control as required
using declarative inputs,
You can
There are two ways to validate form input: input.callback and input.promise.
If (and only if) you specify a callback (or a promise), the submit-button is disabled and the callback will determine when it should be enabled.
Callbacks are triggered whenever any of the inputs changes state, and the result will be handled as a boolean; i.e if you return true
the submit-button will be enabled. The callback passes two params :
callback: function(state, dialog) {
return true || false
}
stateis an object literal containing{ name: value }for all inputs.dialogis the dialog's actualHTMLDialogElement, which can be used to manipulate input fields on the form (or anything else).
An example of using both, here the submit button is enabled only when the user type 42
callback
Example: You are not allowed to submit before you have entered the secret number 47 :
const options = {
headers: { input: 'What is 42 with inflation?' },
icons: { input: '🖖' },
input: {
inputs: [
{ type: 'input', inputType: 'password', label: '', name: 'star-trek-number' }
],
callback: function(state) {
return parseInt(state['star-trek-number']) === 47
}
}
}
Dlg.input(null, options).then(input => {
console.log(input)
})
promise
A promise should be used when you have more complex / time consuming validation schemes. For example, if you need to lookup an username in your backend, to test if it is already in use. In the below we just mimick a server-side call by using a timeout :
const options = {
headers: { input: 'Enter user name' },
icons: { input: '' },
input: {
inputs: [
{ type: 'input', inputType: 'text', label: '', name: 'username' }
],
promise: function(state) {
return new Promise(function(resolve) {
if (state.username.length < 5) return resolve(false)
const letters = /^[0-9a-zA-Z]+$/
if (!letters.test(state.username)) return resolve(false)
setTimeout(function() {
resolve(true)
}, 1000)
})
}
}
}
Dlg.input('Must be at least 5 characters long and may only contain letters and numbers', options).then(input => {
console.log(input)
})
Login Dialog
Here is an example of how to show a modal login-dialog. Notice the use of autocomplete: off which turns off annoying auto suggestions :
const options = {
headers: { input: 'Login' },
icons: { input: '🔑' },
input: {
inputs: [
{ type: 'input', inputType: 'text', label: 'Username', name: 'username', autocomplete: 'off' },
{ type: 'input', inputType: 'password', label: 'Password', name: 'password' }
],
callback: function(state) {
return state.username.length > 0 && state.password.length > 0
}
}
}
Dlg.input('', options).then(input => {
console.log('login', input)
// { username: '...', password: '...' }
})
File input with preview
You kan combine a image type input with a file type input, and by that create an
image-upload-dialog with preview. It takes a little more options-tweaks than the prior examples:
const options = {
headers: { input: 'Upload Image' },
icons: { input: '🖼' },
input: {
inputs: [
{ type: 'input',
inputType: 'image',
label: 'Preview',
name: 'preview',
alt: 'No image yet',
disabled: 'disabled',
src: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"/>',
style: 'min-height: 30vh; max-height: 50vh; border: 1px solid #dadada; object-fit: contain;'
}, {
type: 'input',
inputType: 'file',
label: 'Image',
name: 'file',
accept: 'image/png, image/gif, image/jpeg'
}
],
callback: function(state, dialog) {
if (state.file) {
dialog.querySelector('input[type="image"]').src = URL.createObjectURL(state.file)
return true
}
}
}
}
Dlg.input('Select image file <br><sup>(PNG, GIF, JPG supported)</sup>', options).then(function(input) {
console.log('image upload', input)
})
Further examples
As example, define the following CSS-class and pass the literal :
dialog.nasa-earth {
background: url(https://explorer1.jpl.nasa.gov/assets/images/galleries/1972_BlueMarble_115334main_image_feature_329_ys_full.jpg);
background-size: cover;
color: yellow;
min-height: 50vh;
font-size: x-large;
}
const options = {
classes: 'nasa-earth lg-height sm-width',
icons: { confirm: '👽' },
headers: { confirm: null },
buttons: { captions: { yes: 'No', no: 'Defently not!' }}
}
Dlg.confirm('Are There Any Chance of Intelligent Life in The Universe, anywhere?', options)
;- it is hard to figure out examples, or maybe I am just stoned.
Image Viewer
Reset button-captions and the header :
const msg = '<img src="https://explorer1.jpl.nasa.gov/assets/images/galleries/1972_BlueMarble_115334main_image_feature_329_ys_full.jpg" style="max-height:75vh;">'
const options = {
headers: { bell: '' },
icons: { bell: '' },
buttons: { captions: { ok: null }}
}
Dlg.bell(msg, options)
Maps example
You can use HTML in headers, buttons and the dialog message itself. Here the informtion()
dialog showing a Google Maps destination :
const msg = 'The Great Pyramid of Giza is the largest Egyptian pyramid and tomb of<br> Fourth Dynasty pharaoh Khufu. Built in the 26th century BC during a<br> period of around 27 years. '
const html = '<div class="mapouter"><div class="gmap_canvas"><iframe width="600" height="500" id="gmap_canvas" src="https://maps.google.com/maps?q=the%20great%20pyramids&t=k&z=13&ie=UTF8&iwloc=&output=embed" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe><a href="https://www.whatismyip-address.com"></a><br><style>.mapouter{position:relative;text-align:right;height:500px;width:600px;}</style><a href="https://www.embedgooglemap.net">embedgooglemap.net</a><style>.gmap_canvas {overflow:hidden;background:none!important;height:500px;width:600px;}</style></div></div>'
const options = {
headers: { information: 'The Great Pyramid of Giza' }
}
Dlg.information(msg + html, options)
X-mas
More suitable if you want to style a certain dialog differently. Here is an example with a kind of "Merry X-mas dialog", that overrule many of the default styles :
dialog.x-mas {
border-radius: 0;
border: 1mm ridge #34a65f !important;
background: linear-gradient(45deg, #bf953f, #fcf6ba, #b38728, #fbf5b7, #aa771c);
}
dialog.x-mas h4 {
font-size: xxx-large;
font-family: 'Mountains of Christmas';
font-weight: 900;
color: #cc231e;
text-shadow: 1px 1px #34A65F;
}
dialog.x-mas .dialog-message {
font-size: medium;
font-family: cursive;
color: #cc231e;
text-shadow: -1px -1px gold;
}
dialog.x-mas button {
background-color: #0f8a5f;
font-size: x-large;
font-weight: bold;
padding: 7px;
color: gold;
border-color: gold;
padding-left: 20px;
padding-right: 20px;
}
Then activate the dialog in code like this :
const options = {
classes: 'x-mas',
headers: { alert: 'Merry Christmas ...' },
icons: { alert : '🎄' },
buttons: { captions: { ok: '✨ Ok ✨' }}
}
const msg = '🎀 🎁 Frohe Weihnachten, メリークリスマス, Joyeux Noël, Feliz Navidad, 聖誕快樂, Buon Natale, Glædelig Jul ...!'
Dlg.alert(msg, options)
Integration
Example, Bootstrap 5
Example, Materialize CSS
Credits
This site was easy to produce, thanks to :
- GitHub, for github pages
- Github-pandoc.css, modified where needed
- Chris Ferdinandi, for the Gumshoe scroll spy
- highlight.js and contributors