diff --git a/demo.html b/demo.html
index 55c172b72..ef6c6b2b1 100644
--- a/demo.html
+++ b/demo.html
@@ -49,6 +49,7 @@
Options
+
@@ -340,6 +341,7 @@ Code
html: '',
bootstrap2: '//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css',
bootstrap3: '//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css',
+ bootstrap4: '//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css',
foundation3: '//cdnjs.cloudflare.com/ajax/libs/foundation/3.2.5/stylesheets/foundation.css',
foundation4: '//cdn.jsdelivr.net/foundation/4.3.2/css/foundation.min.css',
foundation5: '//cdn.jsdelivr.net/foundation/5.0.2/css/foundation.min.css',
diff --git a/src/themes/bootstrap4.js b/src/themes/bootstrap4.js
new file mode 100644
index 000000000..30faccca5
--- /dev/null
+++ b/src/themes/bootstrap4.js
@@ -0,0 +1,173 @@
+JSONEditor.defaults.themes.bootstrap4 = JSONEditor.AbstractTheme.extend({
+ getSelectInput: function(options) {
+ var el = this._super(options);
+ el.className += "form-control";
+ //el.style.width = 'auto';
+ return el;
+ },
+ setGridColumnSize: function(el, size) {
+ el.className = "col-md-" + size;
+ },
+ afterInputReady: function(input) {
+ if (input.controlgroup) return;
+ input.controlgroup = this.closest(input, ".form-group");
+ if (this.closest(input, ".compact")) {
+ input.controlgroup.style.marginBottom = 0;
+ }
+
+ // TODO: use bootstrap slider
+ },
+ getTextareaInput: function() {
+ var el = document.createElement("textarea");
+ el.className = "form-control";
+ return el;
+ },
+ getRangeInput: function(min, max, step) {
+ // TODO: use better slider
+ return this._super(min, max, step);
+ },
+ getFormInputField: function(type) {
+ var el = this._super(type);
+ if (type !== "checkbox") {
+ el.className += "form-control";
+ }
+ return el;
+ },
+ getFormControl: function(label, input, description) {
+ var group = document.createElement("div");
+
+ if (label && input.type === "checkbox") {
+ group.className += " checkbox";
+ label.appendChild(input);
+ label.style.fontSize = "14px";
+ group.style.marginTop = "0";
+ group.appendChild(label);
+ input.style.position = "relative";
+ input.style.cssFloat = "left";
+ } else {
+ group.className += " form-group";
+ if (label) {
+ label.className += " form-control-label";
+ group.appendChild(label);
+ }
+ group.appendChild(input);
+ }
+
+ if (description) group.appendChild(description);
+
+ return group;
+ },
+ getIndentedPanel: function() {
+ var el = document.createElement("div");
+ el.className = "card card-block bg-faded";
+ el.style.paddingBottom = 0;
+ return el;
+ },
+ getFormInputDescription: function(text) {
+ var el = document.createElement("p");
+ el.className = "form-text";
+ el.innerHTML = text;
+ return el;
+ },
+ getHeaderButtonHolder: function() {
+ var el = this.getButtonHolder();
+ el.style.marginLeft = "10px";
+ return el;
+ },
+ getButtonHolder: function() {
+ var el = document.createElement("div");
+ el.className = "btn-group";
+ return el;
+ },
+ getButton: function(text, icon, title) {
+ var el = this._super(text, icon, title);
+ el.className += "btn btn-secondary";
+ return el;
+ },
+ getTable: function() {
+ var el = document.createElement("table");
+ el.className = "table-bordered table-sm";
+ el.style.width = "auto";
+ el.style.maxWidth = "none";
+ return el;
+ },
+
+ addInputError: function(input, text) {
+ if (!input.controlgroup) return;
+ input.controlgroup.className += " has-error";
+ if (!input.errmsg) {
+ input.errmsg = document.createElement("p");
+ input.errmsg.className = "form-text errormsg";
+ input.controlgroup.appendChild(input.errmsg);
+ } else {
+ input.errmsg.style.display = "";
+ }
+
+ input.errmsg.textContent = text;
+ },
+ removeInputError: function(input) {
+ if (!input.errmsg) return;
+ input.errmsg.style.display = "none";
+ input.controlgroup.className = input.controlgroup.className.replace(
+ /\s?has-error/g,
+ ""
+ );
+ },
+ getTabHolder: function() {
+ var el = document.createElement("div");
+ el.innerHTML =
+ "";
+ el.className = "rows";
+ return el;
+ },
+ getTab: function(text) {
+ var el = document.createElement("a");
+ el.className = "list-group-item-action";
+ el.setAttribute("href", "#");
+ el.appendChild(text);
+ return el;
+ },
+ markTabActive: function(tab) {
+ tab.className += " active";
+ },
+ markTabInactive: function(tab) {
+ tab.className = tab.className.replace(/\s?active/g, "");
+ },
+ getProgressBar: function() {
+ var min = 0,
+ max = 100,
+ start = 0;
+
+ var container = document.createElement("div");
+ container.className = "progress";
+
+ var bar = document.createElement("div");
+ bar.className = "progress-bar";
+ bar.setAttribute("role", "progressbar");
+ bar.setAttribute("aria-valuenow", start);
+ bar.setAttribute("aria-valuemin", min);
+ bar.setAttribute("aria-valuenax", max);
+ bar.innerHTML = start + "%";
+ container.appendChild(bar);
+
+ return container;
+ },
+ updateProgressBar: function(progressBar, progress) {
+ if (!progressBar) return;
+
+ var bar = progressBar.firstChild;
+ var percentage = progress + "%";
+ bar.setAttribute("aria-valuenow", progress);
+ bar.style.width = percentage;
+ bar.innerHTML = percentage;
+ },
+ updateProgressBarUnknown: function(progressBar) {
+ if (!progressBar) return;
+
+ var bar = progressBar.firstChild;
+ progressBar.className = "progress progress-striped active";
+ bar.removeAttribute("aria-valuenow");
+ bar.style.width = "100%";
+ bar.innerHTML = "";
+ }
+});