Get input type=text to look like type=password
tl;dr
I have an input with type=text
which I want to show stars like an input with type=password
using only CSS.
Basically I have a form with the following input:
<input type='text' value='hello' id='cake' />
I'm not generating the form, I don't have access to its HTML at all. I do however have access to CSS applied to the page.
What I'd like is for it to behave like type=password
, that is - to show up stars for what the user typed rather than the actual text being typed. Basically, I'd want that aspect (the presentation of user input) to look like a type=password
field.
Since this seems like a presentation only issue, I figured there has to be a way to do this with CSS since it's in its responsibility domain. However - I have not found such a way. I have to support IE8+ but I'd rather have a solution that works for modern browsers only over no solution at all. Extra points for preventing copy/paste functionality but I can live without that.
Note: In case that was not clear I can not add HTML or JavaScript to the page - only CSS.
(Only thing I've found is this question but it's dealing with a jQuery related issue and it has a JavaScript solution)
Well as @ThiefMaster suggested
input.pw {
-webkit-text-security: disc;
}
However, this will work in browsers that are webkit descendants.. Opera, Chrome and Safari, but not much support for the rest, another solution to this is using webfonts.
Use any font editing utility like FontForge to create a font with all the characters to be *
( or any symbol you want ). Then use CSS web fonts to use them as a custom font.
You can create a font made only of dots
@font-face
{
font-family:'dotsfont';
src:url('dotsfont.eot');
src:url('dotsfont.eot?#iefix') format('embedded-opentype'),
url('dotsfont.svg#font') format('svg'),
url('dotsfont.woff') format('woff'),
url('dotsfont.ttf') format('truetype');
font-weight:normal;
font-style:normal;
}
input.myclass
{-webkit-text-security:disc;font-family:dotsfont;}
This might be what you're looking for...
There are many glyphs to define but there might be a simpler way to do that..
You can create a totally empty font and define only the .notdef
glyph (glyph ID 0) which is used as a replacement when another glyph is not defined
As you probably know, it usually looks like this:
So, you should replace that with a dot/asterisk and test what happens with browsers... because i'm not sure if it does work on all of them (some may want to use their own missing glyph replacement). Let me know if you try...
HTH
In WebKit-based browsers you can do so using the -webkit-text-security
property. It even allows you to select the shape of the bullets (disc, circle, square).
input.pw {
-webkit-text-security: disc;
}
Demo
input.pw {
-webkit-text-security: disc;
}
input.pw2 {
-webkit-text-security: circle;
}
input.pw3 {
-webkit-text-security: square;
}
<input type="text" class="pw" value="secret">
<input type="text" class="pw2" value="secret">
<input type="text" class="pw3" value="secret">
However, this is apparently non-standard. At least the Safari CSS docs say it's an "Apple Extension". It works fine in Chrome - obviously - but I don't think any other rendering engine supports it...
You can make a fake password input with type text using a custom font. the following works in chrome, firefox, edge ...
@font-face {
font-family: 'password';
font-style: normal;
font-weight: 400;
src: url(https://jsbin-user-assets.s3.amazonaws.com/rafaelcastrocouto/password.ttf);
}
input.key {
font-family: 'password';
width: 100px; height: 16px;
}
<p>Password: <input class="key" type="text" autocomplete="off" /></p>
Thanks to @rafaelcastrocouto Original Answer
I ended up in this thread a lot of times recently. My solution is utilizing JQuery
input event (though it can also be written in raw JS
or even in C#
(Blazor
) should you need it, the idea would be the same):
The core part is:
if (isPasswordVisible) { // if password is visible, then simply update value stored in the dictionary
value = newValue;
passwordInputsValues[$passwordInput.attr("my-guid")] = value;
} else { // else compute and update stored value
const newValueUntilCaret = newValue.take(caretPosition); // take chars before the caret
const unchangedCharsAtStart = newValueUntilCaret.takeWhile(c => c === "●").length; // count unchanged chars from the beginning
const unchangedCharsAtEnd = newValue.skip(caretPosition).length; // count unchanged chars after the caret
const insertedValue = newValueUntilCaret.skip(unchangedCharsAtStart); // get newly added string if any
value = oldValue.take(unchangedCharsAtStart) + insertedValue + oldValue.takeLast(unchangedCharsAtEnd); // create new value as concatenation of old value left part, new string and old value right part
passwordInputsValues[$passwordInput.attr("my-guid")] = value; // store newly created value in the dictionary
$passwordInput.prop("value", value.split("").map(_ => "●").join("")); // set value of the input to new masked value
$passwordInput[0].setSelectionRange(caretPosition, caretPosition); // set caret position to match the appropriate position
}
Below is the complete code of an example password control (I will try to update it if any problems arise):
// Utils
var guid = () => {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === "x" ? r : r & 0x3 | 0x8;
return v.toString(16);
});
}
// Array Extensions
Object.defineProperty(Array.prototype, "skip", {
value: function (n) {
if (typeof (n) !== "number") {
throw new Error("n is not a number");
}
return this.slice(n);
},
writable: true,
configurable: true
});
Object.defineProperty(Array.prototype, "take", {
value: function (n) {
if (typeof (n) !== "number") {
throw new Error("n is not a number");
}
return this.slice(0, n);
},
writable: true,
configurable: true
});
Object.defineProperty(Array.prototype, "takeLast", {
value: function (n) {
if (typeof (n) !== "number") {
throw new Error("n is not a number");
}
return this.slice(Math.max(this.length - n, 0));
},
writable: true,
configurable: true
});
Object.defineProperty(Array.prototype, "takeWhile", {
value: function (condition) {
if (typeof (condition) !== "function") {
throw new Error("condition is not a function");
}
const arr = [];
for (let el of this) {
if (condition(el))
arr.push(el);
else
break;
}
return arr;
},
writable: true,
configurable: true
});
// String Extensions
Object.defineProperty(String.prototype, "skip", {
value: function (n) {
return this.split("").skip(n).join("");
},
writable: true,
configurable: true
});
Object.defineProperty(String.prototype, "take", {
value: function (n) {
return this.split("").take(n).join("");
},
writable: true,
configurable: true
});
Object.defineProperty(String.prototype, "takeLast", {
value: function (n) {
return this.split("").takeLast(n).join("");
},
writable: true,
configurable: true
});
Object.defineProperty(String.prototype, "takeWhile", {
value: function (condition) {
return this.split("").takeWhile(condition).join("");
},
writable: true,
configurable: true
});
// JQuery Document Ready
$(document).ready(function() {
let isPasswordVisible = false;
const passwordInputsValues = {};
for (let $pi of $(".my-password-input").toArray().map(pi => $(pi))) {
const uid = guid();
$pi.attr("my-guid", uid);
passwordInputsValues[uid] = $pi.prop("value");
}
$(document).on("input", ".my-password-input", async function(e) {
const $passwordInput = $(this);
const newValue = $passwordInput.prop("value");
const oldValue = passwordInputsValues[$passwordInput.attr("my-guid")] || ""; // first time it will be undefined
const caretPosition = Math.max($passwordInput[0].selectionStart, $passwordInput[0].selectionEnd);
let value;
if (isPasswordVisible) {
value = newValue;
passwordInputsValues[$passwordInput.attr("my-guid")] = value;
} else {
const newValueUntilCaret = newValue.take(caretPosition);
const unchangedCharsAtStart = newValueUntilCaret.takeWhile(c => c === "●").length;
const unchangedCharsAtEnd = newValue.skip(caretPosition).length;
const insertedValue = newValueUntilCaret.skip(unchangedCharsAtStart);
value = oldValue.take(unchangedCharsAtStart) + insertedValue + oldValue.takeLast(unchangedCharsAtEnd);
passwordInputsValues[$passwordInput.attr("my-guid")] = value;
$passwordInput.prop("value", value.split("").map(_ => "●").join(""));
$passwordInput[0].setSelectionRange(caretPosition, caretPosition);
}
});
$(document).on("click", ".my-btn-toggle-password-visibility", function() {
const $btnTogglePassword = $(this);
const $iconPasswordShown = $btnTogglePassword.find(".my-icon-password-shown");
const $iconPasswordHidden = $btnTogglePassword.find(".my-icon-password-hidden");
const $passwordInput = $btnTogglePassword.parents(".my-input-group").first().children(".my-password-input").first();
const value = passwordInputsValues[$passwordInput.attr("my-guid")];
if (!isPasswordVisible) {
$iconPasswordHidden.removeClass("my-d-flex").addClass("my-d-none");
$iconPasswordShown.removeClass("my-d-none").addClass("my-d-flex");
$passwordInput.prop("value", value);
isPasswordVisible = true;
} else {
$iconPasswordShown.removeClass("my-d-flex").addClass("my-d-none");
$iconPasswordHidden.removeClass("my-d-none").addClass("my-d-flex");
$passwordInput.prop("value", value.split("").map(_ => "●").join(""));
isPasswordVisible = false;
}
});
});
body {
padding-top: 0;
color: white;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 16px;
font-weight: 400;
line-height: 1.5;
text-align: left;
height: 100%;
max-height: 100%;
background-image: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAAAAABURb1YAAAFnklEQVR4AWWXCY4cwXbE6h4JMhCvMPc/ov+3Ox9QsLCGpKlFHILd+UQ559AGDkBSFXWmomonqkL/XuUcIPUcONjHCJyE36KaqNiaRnVCG9TGpgIU9hIfohCBVGj24ammo04lU3Fy0NZDAyZi5AElHjgEGlRbG1WjraIt7TmADQkAaoEHsOXAAaeqat9RVTqjosyEH5W+DQCQt8BzIJMfXJI3e5tGRefSlRn3NrO3scnvNl26FN5RITGNfjm3GM/+FhQgnlQeImC+dEm8rzHVTNGL9EADVsTAIfGJBziRy7RlmcZU1IYpHIj+4JrWym8/XSwzompmFNVOFdTOAAfs61KZ6eXcp57fykQUOxNBmTesz2HpCnCg3UV/bA7VRpWGNqKtrYLTs0ilCjgxiZxjeLC5dDPBFn4Pn2ja6BRUL10DTgET4Focq8rn8casxV4E5tIlZvytqtfivCOKZrqc38G7egnkXecyPZdzubc57VtUnbfL+a0u3b1N3d9Mci7cKocHoh3xihLQ6fXUlgTOOfVkXSUFINoWHhqxE23RTmCqtoEJaITkIrUCkU9fnqpqm+40VSXS3CLaSzfGBb4rPuOSfN0ylLve6/jpcrbL2dTlnOdv5AamKjLvLN1XruMNS/cul/hx8twKpKYRaE2DOp8+c+lW7uVXVQ7Vx0x1G3HXjNocsHCac7NbWc6egwJVHu2kRTVNC2pqv2U4XNsv5/yWJlV9+NJ1C7xluInlcr7rK+Kg+qy7onQqgnbi7UHcS3qXjdw3bO5tumycYlVssMI5EXXpRnatPK362KodtVU60cm+eQT10sVLFwJYlVaf7UGSoi5dQF3DvnTX3UvXTPO8quKH7jsXhm/PxfKWZXqBM5sS33kmKtC31Q05fI095i5IPpyzRXqSy1Sr17p1F7NE8vMtnNbLpVVtk8c0OrfF0sKxHnIpYgHW4puMomTz8mgmrSjpvrxhX97l7LqLpl7bW9BH9/MNzF8XwRqL8/m7c30eFYV5VX3QTquK06XbLt16F4ks+8Z9m6gPtpcuk3OvibCekng4lzMHi9MoqZmKj9tdbQCbc7bcEA+oEJduRNNL1waf1tuI9MDBUK8t3k9uFz3rrqadO9Ln9TL/ezlwDvky3TW7ZmXfpc77TN0yhOXnP939LvVDV9s+tgLTm0TqYd1lea3PRFuVjLa562HdPbCfascGrvO3DMeIATVqVRO8vfHrbupdfo1dzlXUdkVLZsS1mLPG3rWcl+4B1l1537h0VXzU1tyLG7cCu+LSrSqo06hocy3WCcil65qHDbuEYzQN2HpT3jFt9LEFvHTP1nxbXOEkbhkKmd+6BTfVZ/I143z/Fyz6Za92o0I7I6gzfeYAHPwy/Rb43L9TLtMt8CtwW1xvQvbR1g9n7qJRNN1kdxcdH7cM58Szxq679MdLOhE72t6VNEpHHkzwRyjBcGth+dE99pIkVbWaEWmvnQ8Yl7Mp59zpRWXdNo6odrO754Zzvxf/010nP7onb1TR+bgbVPB9PcADJvH/043Kvk0UzLRxQ6UiaeTAcyxYl2lyrrvZv8v2wE7U1uVcST0821jJeIDKWuyxiqnuko7q3+zJYXJ46nV3D67EyL+lQ9F2qkr+3kUafQpwYE8T329p6y7OX1TF97fQv/c+l87zj7Hsgpig4qRLd9fMPs74rLGyCzRbhmVqpy7nqvPmTD1gvKe7b2U8u1QzmtuDuEtbgT3c8QBJveo6cueFkUwv59nVmfFw1jl4/unuOT9qERV76ers8n23z/c8/Xy7a4lwwLplmGusbRp33Up13+ZDN3KPxNVMhY62vUQyRTse6+cKHtZYjIfDh260Cjbb3Vy6R8H8SMBzqSwrsB1UTacoknlnOb8V4JD9Njfpwz/fHjpBUeevcAtcl65sqXPX9D87RUTLc/dfOgAAAABJRU5ErkJggg==);
background-clip: border-box;
background-origin: padding-box;
background-attachment: scroll;
background-repeat: repeat;
background-size: auto;
background-position: left top;
}
.snippet-container {
background: linear-gradient(135deg, #202020, black);
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 200px;
}
.my-password-input {
background: linear-gradient(to bottom, #303030, #000000);
color: white;
display: block;
position: relative;
box-sizing: border-box;
padding: 5px 9px;
line-height: 24px;
height: 34px;
box-shadow: inset 0 0 0 1px #404040;
font-size: 16px;
font-weight: 400;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
transition: all .15s ease-in-out;
width: 100%;
border: none;
}
.my-password-input:enabled:focus {
color: white;
box-shadow: inset 0 0 0 1px #404040, 0 0 6px 2px blue;
outline: none;
}
.my-input-group {
position: relative;
}
.my-input-group > .my-input-group-prepend {
display: flex;
position: absolute;
left: 0;
top: 0;
}
.my-input-group > .my-input-group-append {
display: flex;
position: absolute;
right: 0;
top: 0;
}
.my-icon {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.my-input-group > .my-input-group-prepend > .my-icon,
.my-input-group > .my-input-group-append > .my-icon {
width: auto;
height: 16px;
max-width: none;
max-height: 16px;
flex: 0 0 auto;
margin: 9px;
}
.my-input-group > .my-input-group-prepend > .my-icon > svg,
.my-input-group > .my-input-group-append > .my-icon > svg {
height: 100%;
width: auto;
margin: 0;
padding: 0;
overflow: hidden;
}
.my-input-group > .my-input-group-prepend > .my-btn,
.my-input-group > .my-input-group-append > .my-btn {
height: 100% !important;
width: auto;
}
button:enabled {
cursor: pointer;
}
.my-btn {
background: linear-gradient(to bottom, #303030, #000000);
color: white;
position: relative;
box-sizing: border-box;
padding: 5px;
line-height: 24px;
height: 34px;
font-size: 16px;
font-weight: 400;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
transition: all .15s ease-in-out;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border: none;
box-shadow: 0 0 0 0 #FFFFFF, inset 0 0 0 1px #404040;
}
.my-btn-primary {
color: #fff;
background: linear-gradient(to bottom, #00008B, #000000);
box-shadow: 0 0 0 0 #FFFFFF, inset 0 0 0 1px #0000FF;
}
.my-btn-primary:hover:enabled {
box-shadow: 0 0 6px 2px #FFFFFF, inset 0 0 0 1px #FFFFFF;
background: linear-gradient(to top, #00008B, #000000);
}
.my-btn > .my-icon {
margin: 4px;
width: auto;
height: 16px;
max-width: none;
max-height: 16px;
flex: 0 0 auto;
}
.my-btn > .my-icon > svg {
height: 100%;
width: auto;
}
.my-d-none {
display: none !important;
}
.my-d-flex {
display: flex !important;
}
::-webkit-input-placeholder {
color: #404040;
font-style: italic;
}
:-moz-placeholder {
color: #404040;
font-style: italic;
}
::-moz-placeholder {
color: #404040;
font-style: italic;
}
:-ms-input-placeholder {
color: #404040;
font-style: italic;
}
::-moz-selection {
background-color: #f8b700;
color: #352011;
}
::selection {
background-color: #f8b700;
color: #352011;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="snippet-container">
<div class="my-input-group">
<input type="text" placeholder="Password..." class="my-password-input" style="padding-left: 38px; padding-right: 47px;">
<div class="my-input-group-prepend">
<div class="my-icon" style="">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="">
<path d="M336 32c79.529 0 144 64.471 144 144s-64.471 144-144 144c-18.968 0-37.076-3.675-53.661-10.339L240 352h-48v64h-64v64H32v-80l170.339-170.339C195.675 213.076 192 194.968 192 176c0-79.529 64.471-144 144-144m0-32c-97.184 0-176 78.769-176 176 0 15.307 1.945 30.352 5.798 44.947L7.029 379.716A24.003 24.003 0 0 0 0 396.686V488c0 13.255 10.745 24 24 24h112c13.255 0 24-10.745 24-24v-40h40c13.255 0 24-10.745 24-24v-40h19.314c6.365 0 12.47-2.529 16.971-7.029l30.769-30.769C305.648 350.055 320.693 352 336 352c97.184 0 176-78.769 176-176C512 78.816 433.231 0 336 0zm48 108c11.028 0 20 8.972 20 20s-8.972 20-20 20-20-8.972-20-20 8.972-20 20-20m0-28c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z" style="fill: white"></path>
</svg>
</div>
</div>
<div class="my-input-group-append">
<button class="my-btn my-btn-primary my-btn-toggle-password-visibility" style="width: 38px">
<div class="my-icon my-icon-password-shown my-d-none" style="">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" style="">
<path d="M288 288a64 64 0 0 0 0-128c-1 0-1.88.24-2.85.29a47.5 47.5 0 0 1-60.86 60.86c0 1-.29 1.88-.29 2.85a64 64 0 0 0 64 64zm284.52-46.6C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 96a128 128 0 1 1-128 128A128.14 128.14 0 0 1 288 96zm0 320c-107.36 0-205.46-61.31-256-160a294.78 294.78 0 0 1 129.78-129.33C140.91 153.69 128 187.17 128 224a160 160 0 0 0 320 0c0-36.83-12.91-70.31-33.78-97.33A294.78 294.78 0 0 1 544 256c-50.53 98.69-148.64 160-256 160z" style="fill: white"></path>
</svg>
</div>
<div class="my-icon my-icon-password-hidden" style="">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" style="">
<path d="M637 485.25L23 1.75A8 8 0 0 0 11.76 3l-10 12.51A8 8 0 0 0 3 26.75l614 483.5a8 8 0 0 0 11.25-1.25l10-12.51a8 8 0 0 0-1.25-11.24zM320 96a128.14 128.14 0 0 1 128 128c0 21.62-5.9 41.69-15.4 59.57l25.45 20C471.65 280.09 480 253.14 480 224c0-36.83-12.91-70.31-33.78-97.33A294.88 294.88 0 0 1 576.05 256a299.73 299.73 0 0 1-67.77 87.16l25.32 19.94c28.47-26.28 52.87-57.26 70.93-92.51a32.35 32.35 0 0 0 0-29.19C550.3 135.59 442.94 64 320 64a311.23 311.23 0 0 0-130.12 28.43l45.77 36C258.24 108.52 287.56 96 320 96zm60.86 146.83A63.15 63.15 0 0 0 320 160c-1 0-1.89.24-2.85.29a45.11 45.11 0 0 1-.24 32.19zm-217.62-49.16A154.29 154.29 0 0 0 160 224a159.39 159.39 0 0 0 226.27 145.29L356.69 346c-11.7 3.53-23.85 6-36.68 6A128.15 128.15 0 0 1 192 224c0-2.44.59-4.72.72-7.12zM320 416c-107.36 0-205.47-61.31-256-160 17.43-34 41.09-62.72 68.31-86.72l-25.86-20.37c-28.48 26.28-52.87 57.25-70.93 92.5a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448a311.25 311.25 0 0 0 130.12-28.43l-29.25-23C389.06 408.84 355.15 416 320 416z" style="fill: white"></path>
</svg>
</div>
</button>
</div>
</div>
</div>