Simple Vue.js Contact Form
A Pen by Matthew Daniel Brown on CodePen.
| <div id="app"> | |
| <form class="vue-form" @submit.prevent="submit"> | |
| <div class="error-message"> | |
| <p v-show="!email.valid">Oh, please enter a valid email address.</p> | |
| </div> | |
| <fieldset> | |
| <legend>Vue Contact Form</legend> | |
| <div> | |
| <label class="label" for="name">Name</label> | |
| <input type="text" name="name" id="name" required="" v-model="name"> | |
| </div> | |
| <div> | |
| <label class="label" for="email">Email</label> | |
| <input type="email" name="email" id="email" required="" | |
| :class="{ email , error: !email.valid }" | |
| v-model="email.value"> | |
| </div> | |
| <div> | |
| <h4>Team Member</h4> | |
| <p class="select"> | |
| <select class="budget" v-model="selection.member"> | |
| <option value="0">Sarah Drasner</option> | |
| <option value="1">Evan You</option> | |
| </select> | |
| </p> | |
| </div> | |
| <div> | |
| <h4>Framework</h4> | |
| <ul class="vue-form-list"> | |
| <li> | |
| <input type="radio" name="radio-1" id="radio-1" value="angular" | |
| v-model="selection.framework"> | |
| <label for="radio-1">AngularJS</label> | |
| </li> | |
| <li> | |
| <input type="radio" name="radio-2" id="radio-2" value="react" | |
| v-model="selection.framework"> | |
| <label for="radio-2">ReactJS</label> | |
| </li> | |
| <li> | |
| <input type="radio" name="radio-3" id="radio-3" value="vue" | |
| v-model="selection.framework"> | |
| <label for="radio-3">VueJS</label> | |
| </li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4>Features</h4> | |
| <ul class="vue-form-list"> | |
| <li v-for="(feature, index) in features"> | |
| <input type="checkbox" | |
| :value="feature" | |
| :id="'cb-feature-'+index" | |
| v-model="selection.features"> | |
| <label :for="'cb-feature-'+index">{{feature}}</label> | |
| </li> | |
| <li> | |
| <input type="checkbox" id="checkbox-all" @click="checkAll"> | |
| <label for="checkbox-all">Check All</label> | |
| </li> | |
| </ul> | |
| </div> | |
| <div> | |
| <label class="label" for="textarea">Message with Counter</label> | |
| <textarea class="message" name="textarea" id="textarea" required="" | |
| v-model="message.text" | |
| :maxlength="message.maxlength"></textarea> | |
| <span class="counter">{{ message.text.length }} / {{ message.maxlength }}</span> | |
| </div> | |
| <div> | |
| <input type="submit" value="Send Form"> | |
| </div> | |
| </fieldset> | |
| </form> | |
| <div class="debug"> | |
| <pre><code>{{ $data }}</code></pre> | |
| </div> | |
| </div> |
| // Regular expression from W3C HTML5.2 input specification: | |
| // https://www.w3.org/TR/html/sec-forms.html#email-state-typeemail | |
| var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; | |
| new Vue({ | |
| // root node | |
| el: "#app", | |
| // the instance state | |
| data: function() { | |
| return { | |
| name: "John Doe", | |
| email: { | |
| value: "jo@hnd.oe", | |
| valid: true | |
| }, | |
| features: ["Reactivity", "Encapsulation", "Data Binding"], | |
| selection: { | |
| member: "0", | |
| framework: "vue", | |
| features: [] | |
| }, | |
| message: { | |
| text: `Dear Mr. President,\n...`, | |
| maxlength: 255 | |
| }, | |
| submitted: false | |
| }; | |
| }, | |
| methods: { | |
| // submit form handler | |
| submit: function() { | |
| this.submitted = true; | |
| }, | |
| // validate by type and value | |
| validate: function(type, value) { | |
| if (type === "email") { | |
| this.email.valid = this.isEmail(value) ? true : false; | |
| } | |
| }, | |
| // check for valid email adress | |
| isEmail: function(value) { | |
| return emailRegExp.test(value); | |
| }, | |
| // check or uncheck all | |
| checkAll: function(event) { | |
| this.selection.features = event.target.checked ? this.features : []; | |
| } | |
| }, | |
| watch: { | |
| // watching nested property | |
| "email.value": function(value) { | |
| this.validate("email", value); | |
| } | |
| } | |
| }); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script> |
| @import url("https://fonts.googleapis.com/css?family=Source+Code+Pro:300,400"); | |
| *, | |
| *::after, | |
| *::before { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| color: #fff; | |
| background: #949c4e; | |
| background: linear-gradient( | |
| 115deg, | |
| rgba(86, 216, 228, 1) 10%, | |
| rgba(159, 1, 234, 1) 90% | |
| ); | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, | |
| Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, | |
| sans-serif; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| html, | |
| body, | |
| .container { | |
| min-height: 100vh; | |
| } | |
| .center { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| a { | |
| color: #2c3e50; | |
| text-decoration: none; | |
| } | |
| header { | |
| position: relative; | |
| height: 150px; | |
| padding-top: 100px; | |
| } | |
| header h1 { | |
| text-align: center; | |
| font-size: 2.4rem; | |
| font-weight: 300; | |
| } | |
| #app { | |
| display: flex; | |
| } | |
| .vue-form { | |
| font-size: 16px; | |
| width: 500px; | |
| padding: 15px 30px; | |
| border-radius: 4px; | |
| margin: 50px auto; | |
| width: 500px; | |
| background-color: #fff; | |
| box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.3); | |
| } | |
| .vue-form fieldset { | |
| margin: 24px 0 0 0; | |
| } | |
| .vue-form legend { | |
| padding-bottom: 10px; | |
| border-bottom: 1px solid #ecf0f1; | |
| } | |
| .vue-form div { | |
| position: relative; | |
| margin: 20px 0; | |
| } | |
| .vue-form h4, | |
| .vue-form .label { | |
| color: #94aab0; | |
| margin-bottom: 10px; | |
| } | |
| .vue-form .label { | |
| display: block; | |
| } | |
| .vue-form input, | |
| .vue-form textarea, | |
| .vue-form select, | |
| .vue-form label { | |
| color: #2b3e51; | |
| } | |
| .vue-form input[type="text"], | |
| .vue-form input[type="email"], | |
| .vue-form textarea, | |
| .vue-form select, | |
| .vue-form legend { | |
| display: block; | |
| width: 100%; | |
| appearance: none; | |
| } | |
| .vue-form input[type="text"], | |
| .vue-form input[type="email"], | |
| .vue-form textarea, | |
| .vue-form select { | |
| padding: 12px; | |
| border: 1px solid #cfd9db; | |
| background-color: #ffffff; | |
| border-radius: 0.25em; | |
| box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.08); | |
| } | |
| .vue-form input[type="text"]:focus, | |
| .vue-form input[type="email"]:focus, | |
| .vue-form textarea:focus, | |
| .vue-form select:focus { | |
| outline: none; | |
| border-color: #2c3e50; | |
| box-shadow: 0 0 5px rgba(44, 151, 222, 0.2); | |
| } | |
| .vue-form .select { | |
| position: relative; | |
| } | |
| .vue-form .select::after { | |
| content: ""; | |
| position: absolute; | |
| z-index: 1; | |
| right: 16px; | |
| top: 50%; | |
| margin-top: -8px; | |
| display: block; | |
| width: 16px; | |
| height: 16px; | |
| background: url("data:image/svg+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0D%0A%3C%21DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%0D%0A%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%0D%0A%09%20width%3D%2216px%22%20height%3D%2216px%22%20viewBox%3D%220%200%2016%2016%22%20enable-background%3D%22new%200%200%2016%2016%22%20xml%3Aspace%3D%22preserve%22%3E%0D%0A%3Cg%3E%0D%0A%09%3Cpolygon%20fill%3D%22%232c3e50%22%20points%3D%220.9%2C5.5%203.1%2C3.4%208%2C8.3%2012.9%2C3.4%2015.1%2C5.5%208%2C12.6%20%09%22%2F%3E%0D%0A%3C%2Fg%3E%0D%0A%3C%2Fsvg%3E") | |
| no-repeat center center; | |
| pointer-events: none; | |
| } | |
| .vue-form select { | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); | |
| cursor: pointer; | |
| } | |
| .vue-form select::-ms-expand { | |
| display: none; | |
| } | |
| .vue-form .vue-form-list { | |
| margin-top: 16px; | |
| } | |
| .vue-form .vue-form-list::after { | |
| clear: both; | |
| content: ""; | |
| display: table; | |
| } | |
| .vue-form .vue-form-list li { | |
| display: inline-block; | |
| position: relative; | |
| user-select: none; | |
| margin: 0 26px 16px 0; | |
| float: left; | |
| } | |
| .vue-form input[type="radio"], | |
| .vue-form input[type="checkbox"] { | |
| position: absolute; | |
| left: 0; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| margin: 0; | |
| padding: 0; | |
| opacity: 0; | |
| z-index: 2; | |
| } | |
| .vue-form input[type="radio"] + label, | |
| .vue-form input[type="checkbox"] + label { | |
| padding-left: 24px; | |
| } | |
| .vue-form input[type="radio"] + label::before, | |
| .vue-form input[type="radio"] + label::after, | |
| .vue-form input[type="checkbox"] + label::before, | |
| .vue-form input[type="checkbox"] + label::after { | |
| content: ""; | |
| display: block; | |
| position: absolute; | |
| left: 0; | |
| top: 50%; | |
| margin-top: -8px; | |
| width: 16px; | |
| height: 16px; | |
| } | |
| .vue-form input[type="radio"] + label::before, | |
| .vue-form input[type="checkbox"] + label::before { | |
| border: 1px solid #cfd9db; | |
| background: #ffffff; | |
| box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.08); | |
| } | |
| .vue-form input[type="radio"] + label::before, | |
| .vue-form input[type="radio"] + label::after { | |
| border-radius: 50%; | |
| } | |
| .vue-form input[type="checkbox"] + label::before, | |
| .vue-form input[type="checkbox"] + label::after { | |
| border-radius: 0.25em; | |
| } | |
| .vue-form input[type="radio"] + label::after, | |
| .vue-form input[type="checkbox"] + label::after { | |
| background-color: #2c3e50; | |
| background-position: center center; | |
| background-repeat: no-repeat; | |
| box-shadow: 0 0 5px rgba(44, 151, 222, 0.4); | |
| display: none; | |
| } | |
| .vue-form input[type="radio"] + label::after { | |
| background-image: url("data:image/svg+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0D%0A%3C%21DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%0D%0A%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%0D%0A%09%20width%3D%2216px%22%20height%3D%2216px%22%20viewBox%3D%220%200%2016%2016%22%20enable-background%3D%22new%200%200%2016%2016%22%20xml%3Aspace%3D%22preserve%22%3E%0D%0A%3Ccircle%20fill%3D%22%23FFFFFF%22%20cx%3D%228%22%20cy%3D%228%22%20r%3D%223%22%2F%3E%0D%0A%3C%2Fsvg%3E"); | |
| } | |
| .vue-form input[type="checkbox"] + label::after { | |
| background-image: url("data:image/svg+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0D%0A%3C%21--%20Generator%3A%20Adobe%20Illustrator%2018.1.1%2C%20SVG%20Export%20Plug-In%20.%20SVG%20Version%3A%206.00%20Build%200%29%20%20--%3E%0D%0A%3C%21DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%0D%0A%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%0D%0A%09%20width%3D%2216px%22%20height%3D%2216px%22%20viewBox%3D%220%200%2016%2016%22%20enable-background%3D%22new%200%200%2016%2016%22%20xml%3Aspace%3D%22preserve%22%3E%0D%0A%3Cpolyline%20fill%3D%22none%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22square%22%20stroke-miterlimit%3D%2210%22%20points%3D%225%2C8%207%2C10%2011%2C6%20%22%2F%3E%0D%0A%3C%2Fsvg%3E"); | |
| } | |
| .vue-form input[type="radio"]:focus + label::before, | |
| .vue-form input[type="checkbox"]:focus + label::before { | |
| box-shadow: 0 0 5px rgba(44, 151, 222, 0.6); | |
| } | |
| .vue-form input[type="radio"]:checked + label::after, | |
| .vue-form input[type="checkbox"]:checked + label::after { | |
| display: block; | |
| } | |
| .vue-form input[type="radio"]:checked + label::before, | |
| .vue-form input[type="radio"]:checked + label::after, | |
| .vue-form input[type="checkbox"]:checked + label::before, | |
| .vue-form input[type="checkbox"]:checked + label::after { | |
| animation: cd-bounce 0.3s; | |
| } | |
| .vue-form textarea { | |
| min-height: 120px; | |
| resize: vertical; | |
| overflow: auto; | |
| } | |
| .vue-form input[type="submit"] { | |
| border: none; | |
| background: #2c3e50; | |
| border-radius: 0.25em; | |
| padding: 12px 20px; | |
| color: #ffffff; | |
| font-weight: bold; | |
| float: right; | |
| cursor: pointer; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| appearance: none; | |
| } | |
| .no-touch .vue-form input[type="submit"]:hover { | |
| background: #42a2e1; | |
| } | |
| .vue-form input[type="submit"]:focus { | |
| outline: none; | |
| background: #2b3e51; | |
| } | |
| .vue-form input[type="submit"]:active { | |
| transform: scale(0.9); | |
| } | |
| .vue-form .error-message { | |
| height: 35px; | |
| margin: 0px; | |
| } | |
| .vue-form .error-message p { | |
| background: #e94b35; | |
| color: #ffffff; | |
| font-size: 1.4rem; | |
| text-align: center; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| border-radius: 0.25em; | |
| padding: 16px; | |
| } | |
| .vue-form .error { | |
| border-color: #e94b35 !important; | |
| } | |
| .vue-form .counter { | |
| background-color: #ecf0f1; | |
| position: absolute; | |
| right: 0px; | |
| top: 0px; | |
| font-size: 10px; | |
| padding: 4px; | |
| } | |
| .debug { | |
| border-radius: 4px; | |
| margin: 50px auto; | |
| width: 500px; | |
| background-color: #000; | |
| padding: 50px; | |
| background: rgba(0, 0, 0, 0.8); | |
| box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.3); | |
| } | |
| .debug pre { | |
| color: #ffffff; | |
| font-size: 18px; | |
| line-height: 30px; | |
| font-family: "Source Code Pro", monospace; | |
| font-weight: 300; | |
| white-space: pre-wrap; | |
| } | |
| @-webkit-keyframes cd-bounce { | |
| 0%, | |
| 100% { | |
| -webkit-transform: scale(1); | |
| } | |
| 50% { | |
| -webkit-transform: scale(0.8); | |
| } | |
| } | |
| @-moz-keyframes cd-bounce { | |
| 0%, | |
| 100% { | |
| -moz-transform: scale(1); | |
| } | |
| 50% { | |
| -moz-transform: scale(0.8); | |
| } | |
| } | |
| @keyframes cd-bounce { | |
| 0%, | |
| 100% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(0.8); | |
| } | |
| } |
Simple Vue.js Contact Form
A Pen by Matthew Daniel Brown on CodePen.