Browsing Tag: Form

    web design

    Form Design Masterclass — Smashing Magazine

    02/15/2021

    About The Author

    Adam Silver is an interaction designer focused on design systems and inclusive design. He loves to help organizations deliver products and services so that …
    More about
    Adam

    A couple of weeks ago, we organized a Form Design Masterclass, an online workshop with Adam Silver alongside 81 friendly and smart people. Today, Adam shares his experience and details by highlighting what you as an attendee can expect from a Smashing Workshop, and things to keep in mind when running one.

    It took me around six months on and off to write the content for the workshop. After a lot of deliberation, I decided to structure it like I do in my book, Form Design Patterns.

    It was a 4-day workshop split into two 45-minute segments, with 15-minute breaks followed by a 30-minute Q&A with optional homework between days. Each day we set out to solve one big problem. This provided a way to approach the problem like we do in real life: by analyzing and discussing the options before arriving at a good solution.

    Overall, it was a fun experience. I learned a lot and had a great time teaching and chatting with everyone. I’m already looking forward to the next one which is tentatively planned for Summer 2021.

    Some Of The Highlights Of Each Day

    Here’s a quick rundown of each day including some of the highlights.

    Day 1: Nailing The Basics Of Form Design

    On the first day, we designed a simple registration form from scratch. This provided a perfect way to nail the basics of form design. It covered things like label positioning, form styling and input types. At the end of day 1, we had ourselves a registration form that covered the basics and made the form as simple as possible for users.

    My highlight of this session was the question protocol exercise. Instead of focusing on how to artificially save space on forms (by using things like float labels, tooltips, left-aligned labels and placeholder text), we used a spreadsheet to help know why every question is being asked and the best way to elicit the answer.

    Question protocol spreadsheet
    The question protocol spreadsheet to understand why every question is being asked and the best way to elicit the answer (Large preview)

    For our registration form, this meant a thorough analysis of asking for someone’s name, email address and password. And by the end of the exercise we had halved the number of form fields and had clear justification for the ones that remained.

    Registration form: before and after applying a question protocol
    Registration form: before and after applying a question protocol (Large preview)

    Day 2: Form Validation And Writing Good Error Messages

    On the second day, we took our well-designed registration form and looked at how to help users recover from errors in two ways:

    1. We decided when to validate forms and how to display error messages;
    2. We learnt how to write clear, concise, consistent and specific error messages that help users get back on track quickly.

    My highlight of this session was the exercise to redesign the error messages on Smashing Magazine’s very own membership sign up form.

    Sophy Colbert, a content designer who attended the workshop, volunteered to share her new error messages explaining her rationale for each one.

    Sophy Colbert running through her improved error messages
    Sophy Colbert running through her improved error messages (Large preview)

    Both the messages and the rationale were superb, and I think the group got a lot out of it as they could get an insight into Sophy’s content designer mindset.

    Day 3: Redesigning A Real World Checkout Form

    On day 3, we redesigned the ASOS checkout flow from scratch. This included guest checkout (first time experience) and checking out as someone with an account (repeat-use experience). We covered a lot of ground such as whether to use tabs, accordions or radio buttons. And we also looked at single page checkouts versus multi-page checkouts.

    My highlight of this session was that the process of redesigning several interactions, exposed new content design and service design challenges. For example, we converted the tabs that ask the user to specify whether they have an account or not:

    Original design of ASOS page using tabs to let users switch between ‘New to ASOS?’ and ‘Already registered?’ options
    Original design of ASOS page using tabs to let users switch between ‘New to ASOS?’ and ‘Already registered?’ options (Large preview)

    And we redesigned them into a form with radio buttons:

    New design of ASOS page using radio buttons to let users choose whether they have an account or not
    New design of ASOS page using radio buttons to let users choose whether they have an account or not (Large preview)

    And this exposed the problem that in real life, choices are rarely binary. So I asked the group what the missing option was and they rightly said: ‘What if the user can’t remember?’

    New design of ASOS page with the added option of ‘Can’t remember’ to the question ‘Do you have an account or not?’
    New design of ASOS page with the added option of ‘Can’t remember’ to the question ‘Do you have an account or not?’ (Large preview)

    So even though we originally looked at this primarily as an interaction design problem, it became an issue of content and service design.

    All of these problems nicely encapsulated one of the form UX rules: ‘Make friends with other departments’. As designers, we have to work effectively with stakeholders across the organisation to make sure we avoid as much complexity as possible. And this again is where the question protocol really shines.

    Day 4: Using Shorthand Syntax And Designing Long And Complex Forms

    Day 4 was split into two parts which I’ll discuss in reverse order.

    In the second part, we looked at various patterns that help users fill out long and complex forms — the kind of forms that take days, weeks or even months to complete. I was really looking forward to running this because the design challenges around this are interesting and not well trodden.

    In the first part, we redesigned Smashing Magazine’s registration form using shorthand syntax.

    My highlight of this session was that Vitaly, Mr. Smashing Magazine himself, came along to be our business stakeholder. The group asked him questions to work out why the form was designed the way it was and asking why certain questions were asked.

    The Smashing Magazine membership registration form
    The Smashing Magazine membership registration form (Large preview)

    Here are a few examples:

    • Sophy O asked why the country field is being asked for. Vitaly said that it depends on what the user is doing. If the user is buying a book, we need to know where it’s going. And the taxes on the book are based on the destination country.’ This resulted in either removing the field and asking for this information when someone buys the book — or just being clearer in hint text about why we’re asking for this information.
    • Milos Lazarevic questioned the need for the ‘Do you like cats?’ checkbox. And Dana Cottreau and Jaclyn Ziegler enjoyed the playfulness of the checkbox. But I would weigh up the joy it brings some people against the risk of alienating people who are, for example, less digitally savvy or are simply in a rush to access the content.
    • Emma Stotz questioned the use of live validation given all the usability issues that pop up around that. And Vitaly was keen to explore instantly validating the fields on submit instead.

    My Overall Impression

    For me, the workshop went very well overall and I was chuffed with the way things went and the feedback I received from the attendees. Everyone was so friendly, and tolerant of a couple of technical difficulties I had on the first day (thanks again, everyone!). Running the workshop remotely over Zoom has its problems (we won’t talk about how I accidentally left the meeting in a panic by accident on day 1), but actually I found the remote aspect useful on the whole.

    For example, all being connected to Zoom, meant it was seamless for attendees to ask questions while sharing their screen to bring the problems to life.

    I also really enjoyed meeting people across the world, something that would have been difficult with in-person workshops I think. Also, during the break, I got to quickly dash to put my kids to bed, so I imagine that also worked well for the attendees, too.

    But there’s one thing I wish I knew earlier. I was worried, that with such a large group of people (81 to be exact), letting people talk freely would end up in a chaos. As a result, on day 1, I read out and answered group’s questions from the shared Google Doc during the Q&A. This meant that other people’s voices weren’t heard and there was more of a barrier between me and the group.

    This is something I rectified for day 2 and it really made a difference. It was nice to hear people’s voices and thoughts in their own words and it created more of an open dialogue where other people started to answer other people’s questions which I loved.

    I remember Alex Price jumping in once to talk about his experience in dealing with a complicated form that needed to be completed by different people.

    What I’ll Change For Next Time

    While my overall impression of the workshop was very positive, there were some things I’d look to improve for next time.

    1. Show The Basics, Not Learn The Basics

    Day 1 covered a lot of the basics before going into greater detail on the following days, but it bothered me a bit to teach some of these things as I thought many attendees knew a lot of this stuff already. So next time I’d like to acknowledge that some people have come with a lot of knowledge and set the scene as ‘this is how I teach the basics’ as opposed to ‘this is how to learn the basics’ — thanks to Caroline Jarrett for this tip.

    Also, I’ll probably ask the group if there’s any form design approach that they’ve struggled to convince teammates about as that’s certainly something I’ve struggled with before.

    2. Split People Up In Bigger Groups

    One of the exercises involved people splitting up into groups of 2 using the Zoom breakout rooms, but because people came to this workshop from all over the world, some of the people listening were not able to take part in the exercises.

    For example, some people really needed to take a lunch break because their time zone was ahead of mine. This meant one or two people who did want to participate found themselves in a group on their own. Next time, I’d put people into groups of say 4 and make sure the exercises still work.

    3. Add More Group Exercises

    Despite the issue I just mentioned, the group exercises worked well. People enjoyed them, and it sparked some really interesting ideas from the participants. Some people messaged me after saying that they wished there were more group exercises, so I’ll aim to do just that.

    A Poster Of All The Rules

    As we moved through the workshop, we ticked off over 40 rules and principles of form design which brought a nice additional structure to the sessions.

    A few of the attendees asked me if I had a poster of all the rules and I didn’t — so now I’ve made one.

    All 42 rules from the workshop captured in a handy poster
    All 42 rules from the workshop captured in a handy poster. (Download the poster)

    Thanks again to everyone who came for all their contributions. I’m looking forward to the next one.

    Thanks to Caroline Jarrett for not only reviewing every detail of my workshop but for also editing this article.

    Smashing Editorial
    (vf, il)

    Source link

    web design

    The Past, Present And Future Of Native HTML Form Controls — Smashing Magazine

    11/18/2020

    About The Author

    Stephanie is a Design Technologist and Program Manager for Microsoft Edge Developer Experiences. Her design and program management work has been focused on …
    More about
    Stephanie

    Working with native HTML Form Controls has been such a pain point for web developers, from styling to extending them, the limitations are so great that countless dev hours have been spent recreating them. But why are form controls so difficult to work with?

    In this article, Stephanie dives into the past by going back to the beginning of HTML and tracing the evolution of form controls through to the present and the current state of working with them. She shares her thoughts and takes a glimpse at what the future holds for working with these essential pieces of the web.

    Whether it’s an input to search a website or a text input field and submit button for comments on a blog or a checkbox to accept the terms and conditions of a website, form controls are some of the most common components and provide the foundation for interactivity on the web. They are everywhere online and have been since the beginning of HTML.

    They were introduced in the HTML 2.0 specification in 1995, but, despite their early origins, the ease with which developers can style or customize them ranges from extremely easy to nearly impossible. This has led to developers scrapping these native controls altogether and building custom ones from scratch which can be problematic and time-consuming.

    Controls built from scratch lack the features that come with native ones, such as accessibility and security, so there’s a plethora of extra work to make custom controls accessible and secure. We’ll look at the history of form controls that led us to where we are today, the current state of working with them, and a brief glimpse of the work being done to fix this space.

    A Brief History Of HTML Controls

    After the release of the first web browser in the early ’90s from Tim Berners-Lee called WorldWideWeb (later renamed to Nexus), multiple other web browsers were being developed and made available to the public. But the preliminary HTML specification was extremely basic at the time, with only a handful of tags available for text markup.

    As the new vendors started to iterate on their new browsers, each one started to implement new HTML tags to help fill out feature gaps and start to add support for things like images and other interactive elements. This created a growing rift in HTML as there was no standard or specification for it yet.

    In 1993, Berners-Lee and Dan Connolly had worked on defining the first specification for HTML but the draft expired in early 1994. The first HTML Working Group was established after that first draft expired and completed the first specification for HTML 2.0 that would become the basis for HTML Standards going forward.

    The HTML 2.0 specification took note of the current landscape of HTML features across different browsers, and rather than break the web, included those features that were already available in browsers in the spec.

    HTML 2.0 gave the web the first specifications for form functionality for the following form types:

    • form
    • input (type=):
      • text
      • password
      • radio
      • image
      • hidden
      • submit
      • reset
    • select
    • option
    • textarea

    The spec standardized the method for users to enter data into an HTML document and how that data was to be used to perform an action such as logging into a website. It did not, however, define the different parts of the controls or how each control would be constructed and rendered in the browser.

    Technical And Style Limitations

    In 1995, there was no styling language established yet, which is where the first style of limitations with form controls came into play. Browsers had to rely on the operating system to style and render form controls, which at the time meant there was a dependency on the operating system both technically and stylistically.

    This wasn’t something developers could access to customize, and there wasn’t even a way to do so without a styling language. Despite the idea of style sheets in browsers having been around since 1990 with Berners-Lee’s initial browser, not all the browsers that had appeared in the 90s offered a way for developers to style things. If they did, they heavily limited what was style-able.

    But as the web flourished and CSS was established as the styling language for it, form controls were still off the table when it came to styling or customizing them. They were meant to be a reflection of the operating system they were on. That level of styling and customization wasn’t a requirement at the time.

    The CSS 2.1 specification, which waffled between working draft status and candidate recommendation from 2004-2010, even specified that browsers didn’t have to apply CSS to form controls.

    “CSS 2.1 does not define which properties apply to form controls and frames, or how CSS can be used to style them. User agents may apply CSS properties to these elements. Authors are recommended to treat such support as experimental. A future level of CSS may specify this further.”

    UA Conformance, CSS 2.1 Specification, W3C

    It was left entirely up to user agents to provide styling for the form controls. Even as multiple browsers became available on operating systems, rendering wasn’t consistent across browsers.

    A group of native <button> controls from different browsers and operating systems that highlights the varying native styles between the same component.
    Multiple form controls across browsers in 2004 (Large preview)

    As developers started to ask for control over styling various controls, a proposal for the appearance property was included, but later dropped from the CSS Basic User Interface Module Level 3 standard. The appearance proposal was supposed to “provide additional CSS mechanisms to simulate the appearance of various standard form elements” but was never actually implemented as designed and support across browsers varies wildly.

    Developers started to use appearance: none; to remove all native styling and attempt to apply their own CSS to form controls. However, the inconsistency in what can be styled across different form controls once appearance: none; is applied varies widely and inconsistently across browsers, and doesn’t solve the real problem of not being able to style form controls.

    Working With HTML Controls On The Modern Web

    With the pace at which the web evolves, you might think that this problem around styling form controls would be closer to being solved, or at least easier than it was 10-15 years ago. While a handful of form controls can be easily styled by CSS, like the button element, most form controls fall into a bucket of either requiring hacky CSS or are still unable to be styled at all by CSS.

    Despite form controls no longer taking a style or technical dependency on the operating system and using modern rendering technology from the browser, developers are still unable to style some of the most used form control elements such as <select>. The root of this problem lies in the way the specification was originally written for form controls back in 1995.

    The initial specification never standardized the parts that comprise each form control, it only standardized the method to enter data into an HTML document and for that data to be used to perform an action leading to browsers defining how they each built their form controls. Each form control had a standardized purpose, but how that purpose was constructed was up to interpretation.

    Without standardized parts, trying to open the different parts of more complex controls to enable developers to have more control over styling creates issues with cross-browser compatibility. If only one browser has implemented access to different parts of a select to customize the appearance fully, that is ultimately useless for the developer if the end goal is to have controls render the same across all browsers.

    But rendering inconsistency and inability to style consistently across browsers is not the only thing developers are missing from native form controls. There is a lack of extensibility with native controls. Developers cannot add or remove functionality to native controls.

    The video element is a great example of this:

    <video controls width="1080"></video>
    

    The controls attribute is the only way to show or hide the playback controls for the video element with no option to customize or extend what is shown. You either get all the video controls or none leading to a poor experience with the native element.

    Between a severe lack of control over styling and a lack of extensibility, it’s no wonder developers have abandoned using native controls and gone about rebuilding them from scratch.

    When rebuilding from scratch though, a developer has to consider all the different pieces they need to add back into the control to reach parity with a native control’s features to test in the first place. With accessibility, for example, it’s not just adding the appropriate ARIA roles or attributes, as ARIA only provides the semantics that hook up the different parts to the correct accessibility APIs. Keyboard functionality to tab through a control and focus needs to be added back in on top of ARIA. JavaScript may be required for some of this functionality, so developers need to consider what happens to that control if a user has JavaScript disabled. There are performance implications to consider as more JavaScript is added to controls as well.

    And then there’s developer time going toward testing for accessibility, performance, and security, things that are built in with native controls, and they may not always have the support to properly test those things across all browsers.

    It’s also not a situation where a developer can write that code once and be done with it.

    When a developer creates these custom elements, they also need to be maintained in the long run. What if a piece of the code being used is deprecated from the web platform that breaks functionality? What if accessibility requirements change or evolve? What if there is a security vulnerability? The cost of maintaining the custom elements over time can end up being more expensive and more time-consuming.

    The current state of working with controls on the modern web is that countless developer hours are being lost to rewriting controls from scratch, as custom elements due to a lack of flexibility in customizability and extensibility of native form controls. This is a massive gap in the web platform and has been for years. Finally, something is being done about it.

    Solving The Pain Points Around Form Controls

    Greg Whitworth conducted some research on form controls while working on the Microsoft Edge web platform team last year to validate everything we’ve heard candidly from developers: that native form controls are difficult to work with and don’t meet their needs.

    In an initial survey that had 1,400 respondents, one of the questions asked was which form controls did respondents re-create.

    The top 3 were:

    1. Select (10.7%)
    2. Checkbox (10.2%)
    3. Date (9.5%)

    One of the follow-up questions then asked of respondents was “why are you recreating form controls?” and these were the top 3 responses:

    • 36.6% said they couldn’t change the appearance sufficiently
    • 31.6% wanted to add functionality
    • 27.3% said browser inconsistencies
    Pie chart breakdown of the reasons why controls are created from scratch: 36.6% said they couldn’t change the appearance sufficiently, 31.6% wanted to add functionality
, 27.3% said browser inconsistencies
, 2.8% accessibility issues, 1.8% other
    A breakdown of all the different reasons controls are created from scratch. (Large preview)

    Browser inconsistencies in the context of controls usually relate to appearance. If we group those respondents in with those who marked “couldn’t change appearance sufficiently”, that’s over 60% of respondents sacrificing built-in accessibility and performance with native controls because of appearance.

    Open UI

    There is a huge opportunity to fix this problem space for developers and it’s incredibly important that those in positions to drive a solution for this space do so in the right way. That means working through standards proposals and drafts in the public and with the community’s feedback.

    Open UI is an initiative under the Web Incubator Community Group, that is undertaking the task of documenting control components across design systems and analyzing design system terminology to find patterns and similarities that will help set the path for standards and native support in the browser. This work is being driven by browser vendors, framework authors, design system maintainers and is open to participation by anyone interested in this space.

    This initiative isn’t to standardize how a form control looks, but to standardize form control names, their parts, states, and behaviors. This involves a heavy amount of research for each control, exploring use cases, functionality, and features that developers add to their custom components and deciding when that added functionality equates to a new control (<select> vs combobox is a great example of this). We expect the work being done in Open UI to integrate with other working groups by passing along recommendations and filing issues in the appropriate venues to move this work forward.

    If you’re interested in getting involved in any part of Open UI, check out the GitHub repository and the open issues. Not only is it a great project to dip your toes into if you’re interested in standards work, but the contributions and research will contribute to solving a huge pain point for developers and even designers. If you’re a designer who works with controls and components, this space is also open to you as this work integrates with design system frameworks.

    Standardizing Without Breaking The Web

    Watching as different solutions for enabling customization of control UI have evolved over the last few months, one of the concerns that arose was around breaking native controls already out on the web.

    Existing native controls will continue to work, but with the current proposal, there will be additional code that developers can use to style arbitrary parts of native control, add arbitrary content into any part of native control (with limitations, things like iframes will be blocked), and customize the UI without having to rebuild anything from scratch.

    The ultimate goal is to provide developers with a high degree of flexibility over appearance and extensibility of controls. The initial explainer document on enabling custom control UI is now available for public review. This is the first step towards standardization; these changes are being driven and brought into standards bodies because of feedback from developers, so take a moment to read the explainer and the open questions and provide your feedback or thoughts on GitHub.

    A Promising Future

    Developers and web designers have been asking for years for improved controls and flexibility with styling, but when we really get to the heart of it, there are so many different use cases and extensibility features that developers have implemented in custom components, especially when we start to explore controls like <select>.

    They may seem like a small or arbitrary part of the web, but HTML Controls provide the functionality for logging into a website or submitting an order, and are essential to the web ecosystem we rely on today.

    Meaningful steps are being taken to solve the pain points that developers have had for years with these components and to put standards in place that will allow for the customization that has been so highly requested.

    On browser teams, we’re investing in this space because developer feedback was so consistent and abundant. If you work on the web and are interested in helping to drive this space forward, I invite you to get involved in Open UI or provide feedback on the initial explainer for customizing controls UI. We want to ensure we’re taking into consideration the needs of all developers and making the right decisions as we pursue this space.

    Smashing Editorial
    (jw, ra, il)

    Source link

    web design

    Build And Deploy An Angular Form With Netlify Forms And Edge — Smashing Magazine

    10/14/2020

    About The Author

    Zara Cooper is a software developer who enjoys sharing what she learns as a developer with others. When she’s got time to spare, she enjoys reading a good book …
    More about
    Zara

    Netlify Forms is a form handling feature that receives submissions from HTML forms automatically. In this article, we’ll cover how to use it with Angular reactive forms and how to deploy the finished app on Netlify’s hosting platform, Netlify Edge.

    Creating the frontend, backend, and deployment workflow of an app takes a lot of work. In instances where your app collects only a limited amount of data submissions from its users, building a whole backend may not seem worth the time and effort. An alternative to developing a complete backend is using Netlify Forms. In this tutorial, I’ll explain how you could use an Angular reactive form with Netlify Forms. Since Netlify Forms only work when deployed on Netlify, I’ll also illustrate how to deploy your app on Netlify Edge.

    The Toolkit

    An Angular reactive form is a form that has a structured data model created explicitly within a component class using the ReactiveFormsModule providers. A form model is created for each input element within the form view. This form model is an instance of the FormControl class and it keeps track of the value of the form element. The form model is immutable because whenever a change is made to the model the FormControl instance returns a new data model instead of updating the old model. Its immutability makes change detection more efficient and allows data alteration with observable operators. Since form input elements are directly connected to their form models, updates between them are synchronous and do not rely on UI rendering.

    Netlify is a platform that allows you to build, deploy, and host sites built with various technologies. Sites built with Angular can be hosted on Netlify. Netlify additionally provides a host of tools that simplify, automate, and augment builds and deployments of these sites. We’re going to use two of its products in this tutorial: Netlify Edge and Netlify Forms.

    As described earlier, Netlify Forms is a form handling feature that receives submissions from HTML forms automatically. It does not require any submission processing configuration, like creating APIs, scripts, etc. This feature only works with forms in sites deployed on Netlify. It is enabled by default, further reducing the configuration needed to set up the form submissions. Submission handling is set up during deployment where a site’s HTML files are parsed by Netlify’s build bots.

    Netlify Edge is a global application delivery network on which sites and applications are published. It provides features like A/B testing, rollbacks, staging, and phased rollouts. All deployments on Netlify Edge are atomic, meaning a site is only live when all files have been uploaded/updated and changes to the site are ready. Once a site is deployed, it is assigned a subdomain on netlify.app when deployed to production. Netlify Edge also supports preview and branch deployments (staging, development, etc.).

    Netlify Forms submission-handling works because build bots parse HTML forms on a site during deployment. Client-side Javascript rendered forms like those in compiled Angular sites won’t be found by these bots. So the normal set up for Netlify Forms won’t work with Angular Forms.

    However, there is a work-around to this. To get it to receive submissions, a hidden plain HTML form is added to the index.html file. This form works with the build bots. When submitting the Angular Form, a post request is made to this hidden form which is then captured by Netlify Forms.

    In this article, we will create a reactive form. We’ll also develop a service to make a post request to the hidden HTML form. Lastly, we will deploy the app to Netlify Edge.

    Example

    To illustrate how to build the app, we will take an example of a feedback form common on many websites. We will use this form to collect comments/complaints, questions, and suggestions from users of the site along with their name and email. We shall also use it to collect their rating of the site.

    Requirements

    To follow along with this tutorial, you will need a Netlify account and the Angular CLI installed. If you do not have the CLI, you can install it using npm.

    npm install -g @angular/cli
    

    If you’ve not signed up for a Netlify account yet, you can create one here. Netlify offers sign-up through Github, Gitlab, Bitbucket, or Email. Depending on what deployment method you choose to go with, they may be other requirements. They will be stated under each deployment method.

    Setting Up The App

    To start, we will create the app and call it feedback. When creating it, add routing to it when asked in the prompts.

    ng new feedback
    

    Next, we’ll generate three components: a feedback form, a successful submission message page, and a 404 page. Netlify Forms allow you to navigate to a page upon successful form entry submission. That’s what we’ll use the SuccessComponent for.

    ng g c feedback
    ng g c success
    ng g c page-not-found
    

    After generating the components, we’ll add the routes to each page in the AppRoutingModule within the app-routing.module.ts file.

    const routes: Routes = [
      { path:'', component: FeedbackComponent },
      { path: 'success', component: SuccessComponent },
      { path: '**', component: PageNotFoundComponent }
    ];
    

    We’ll use the FormBuilder service to create our reactive form. This is because it is more convenient and less repetitive than using basic form controls. To have access to it, we’ll need to register the ReactiveFormsModule in the app.module.ts file.

    Since we will be making a post request to the hidden HTML form, we also have to register the HttpClientModule.

    import { ReactiveFormsModule } from '@angular/forms';
    import { HttpClientModule } from '@angular/common/http';
    
    @NgModule({
      imports: [
        // other imports
        ReactiveFormsModule,
        HttpClientModule
      ]
    })
    export class AppModule { }
    

    Proceed to change the contents of app.component.html to just have the router outlet.

    <router-outlet></router-outlet>
    

    The different pages will share some styling. So add the styling below to styles.css.

    html, body {
        height: 100%;
        width: 100%;
        display: flex;
        align-items: flex-start;
        justify-content: center;
    }
    
    h1 {
        margin: 0;
        text-align: center;
    }
    
    h1, p, label {
        font-family: Arial, Helvetica, sans-serif;
    }
    
    p {
        max-width: 25rem;
    }
    
    #container {
        border: none;
        padding: .4rem;
        border-radius: 0;
        flex-direction: column;
        display: flex;
    }
    
    hr {
        width: 80%;
    }
    
    button {
        color: white;
        background-color: black;
        font-size: large;
        padding: .5rem;
        border-radius: .5rem;
        margin-top: 1rem;
    }
    
    @media screen and (min-height: 700px) {
        html, body {
            align-items: center;
            justify-content: center;
        }
    }
    
    @media screen and (min-width: 480px) {
        #container {
            border: .1rem solid lightgray;
            padding: 2rem;
            border-radius: .5rem;
        }
    
        html, body {
            align-items: center;
            justify-content: center;
        }
    }
    

    Create The Reactive Form

    In our FeedbackComponent class, we will begin by importing the FormBuilder service which we’ll use to create the form. We’ll also import the Validators class for form input validation.

    import { FormBuilder, Validators } from '@angular/forms';
    

    We will then inject the FormBuilder service by adding it to the FeedbackComponent constructor.

    constructor(private fb: FormBuilder) { }
    

    Next, we’ll define the form model using the group method of the injected FormBuilder service. We’ll also add an errorMsg property to hold any errors we may encounter when submitting the form input. Also included is a closeError method that will close the error alert that displays on the form.

    Each control in the form model will be verified using validators from the Validators class. If any of the inputs fail validation, the form will be invalid and submission will be disabled. You can choose to add multiple validators to a form control like in the case of the email control.

    export class FeedbackComponent {
      feedbackForm = this.fb.group({
        firstName: ['', Validators.required],
        lastName: ['', Validators.required],
        email: ['', [Validators.email, Validators.required]],
        type: ['', Validators.required],
        description: ['', Validators.required],
        rating: [0, Validators.min(1)]
      });
    
      errorMsg = '';
    
      closeError() {
        this.errorMsg = '';
      }
    
      // ...
    }
    

    In the component’s template (feedback.component.html), we shall add this.

    <div id="container">
      <div class="error" [class.hidden]="errorMsg.length == 0">
        <p>{{errorMsg}}</p>
        <span (click)="closeError()" class="close">✖︎</span>
      </div>
      <h1>Feedback Form</h1>
      <hr>
      <p>We’d like your feedback to improve our website.</p>
      <form [formGroup]="feedbackForm" name="feedbackForm" (ngSubmit)="onSubmit()">
        <div id="options">
          <p class="radioOption">
            <input formControlName="type" type="radio" id="suggestion" name="type" value="suggestion">
            <label for="suggestion">Suggestion</label><br>
          </p>
          <p class="radioOption">
            <input formControlName="type" type="radio" id="comment" name="type" value="comment">
            <label for="comment">Comment</label><br>
          </p>
          <p class="radioOption">
            <input formControlName="type" type="radio" id="question" name="type" value="question">
            <label for="question">Question</label><br>
          </p>
        </div>
        <div class="inputContainer">
          <label>Description:</label>
          <textarea rows="6" formControlName="description"></textarea>
        </div>
        <div class="inputContainer">
          <div id="ratingLabel">
            <label>How would you rate our site?</label>
            <label id="ratingValue">{{feedbackForm.value?.rating}}</label>
          </div>
          <input formControlName="rating" type="range" name="rating" max="5">
        </div>
        <div class="inputContainer">
          <label>Name:</label>
          <div class="nameInput">
            <input formControlName="firstName" type="text" name="firstName" placeholder="First">
            <input formControlName="lastName" type="text" name="lastName" placeholder="Last">
          </div>
        </div>
        <div class="inputContainer">
          <label>Email:</label>
          <input formControlName="email" type="email" name="email">
        </div>
        <div class="inputContainer">
          <button type="submit" [disabled]="feedbackForm.invalid">Submit Feedback</button>
        </div>
      </form>
    </div>
    

    Note that the form element should have the [formGroup]="feedbackForm" attribute corresponding to the model we just created. Also, each of the input elements should have a formControlName="" attribute corresponding to its counterpart form control in the model.

    To style the form, add this tofeedback.component.css.

    #options {
        display: flex;
        flex-direction: column;
    }
    
    #options label {
        margin: 0 0 0 .2rem;
    }
    
    .radioOption {
        margin: 0 0 .2rem 0;
    }
    
    .inputContainer {
        display: flex;
        flex-direction: column;
        margin: .5rem 0 .5rem 0;
    }
    
    label {
        margin: .5rem 0 .5rem 0;
    }
    
    .nameInput {
        display: flex;
        flex-direction: column;
    }
    
    button:disabled {
        cursor: not-allowed;
        pointer-events: all;
        background-color: slategrey;
    }
    
    #ratingLabel {
        display: flex;
        justify-content: space-between;
        margin: .5rem 0 .5rem 0;
    }
    
    #ratingValue {
        font-weight: bolder;
        font-size: large;
        border: .1rem solid lightgray;
        padding: .4rem .6rem .1rem .6rem;
        margin: 0;
        vertical-align: middle;
        border-radius: .3rem;
    }
    
    .error {
        color: darkred;
        background-color: lightsalmon;
        border: .1rem solid crimson;
        border-radius: .3rem;
        padding: .5rem;
        text-align: center;
        margin: 0 0 1rem 0;
        display: flex;
        width: inherit;
    }
    
    .error p {
        margin: 0;
        flex-grow: 1;
    }
    
    textarea, input {
        margin: .1rem;
        font-family: Arial, Helvetica, sans-serif;
        padding: 5px;
        font-size: medium;
        font-weight: lighter;
    }
    
    .close {
        cursor: default;
    }
    
    .hidden {
        display: none;
    }
    
    @media screen and (min-width: 480px) {
        #options {
            flex-direction: row;
            justify-content: space-around;
        }
    
        .nameInput {
            flex-direction: row;
            justify-content: space-between;
        }
    }
    

    This is what the form will look like:

    Feedback Form
    Screenshot of Feedback Form (Large preview)

    Adding A Hidden HTML Form

    As stated earlier, we need to add a hidden HTML form that the Netlify Forms build bots can parse. Submissions will then be sent from our reactive form to the hidden HTML form. The HTML form is put in the index.html file.

    This form should have the same name as the reactive form. Additionally, it should contain three other attributes: netlify, netlify-honeypot, and hidden. The bots look for any forms that have the netlify attribute so that Netlify can process inputs from them. The netlify-honeypot attribute is added to prevent captchas from being shown when a submission is made and enables extra spam protection.

    <!doctype html>
    <html lang="en">
    <!-- Head -->
     <body>
      <form name="feedbackForm" netlify netlify-honeypot="bot-field" hidden>
        <input type="text" name="firstName"/>
        <input type="text" name="lastName"/>
        <input type="text" name="email"/>
        <input type="text" name="feedbackType"/>
        <input type="text" name="description"/>
        <input type="text" name="rating"/>
      </form>
      <app-root></app-root>
     </body>
    </html>
    

    It’s important to note that since you can’t set the value of file input elements, you can’t upload a file using this method.

    Making A Post Request To The Hidden Form

    To send a submission from the reactive form to the HTML form, we’ll make a post request containing the submission to index.html. The operation will be performed in the onSubmit method of the FeedbackComponent.

    However, before we can do that, we need to create two things: a Feedback interface and a NetlifyFormsService. Let’s start with the interface.

    touch src/app/feedback/feedback.ts
    

    The contents of this file will be:

    export interface Feedback {
       firstName: string;
       lastName: string;
       email: string;
       type: string;
       description: string;
       rating: number;
    }
    

    The NetlifyFormsService will contain a public method to submit a feedback entry, a private method to submit a generic entry, and another private one to handle any errors. You could add other public methods for additional forms.

    To generate it, run the following:

    ng g s netlify-forms/nelify-forms
    

    The submitEntry method returns an Observable<string> because Netlify sends a HTML page with a success alert once we post data to the form. This is the service:

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
    import { Feedback } from '../feedback/feedback';
    import { Observable, throwError } from 'rxjs';
    import { catchError } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root'
    })
    export class NetlifyFormsService {
    
      constructor(private http: HttpClient) { }
    
      submitFeedback(fbEntry: Feedback): Observable {
        const entry = new HttpParams({ fromObject: {
          'form-name': 'feedbackForm',
          ...fbEntry,
          'rating': fbEntry.rating.toString(),
        }});
    
        return this.submitEntry(entry);
      }
    
      private submitEntry(entry: HttpParams): Observable {
        return this.http.post(
          '/',
          entry.toString(),
          {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            responseType: 'text'
          }
        ).pipe(catchError(this.handleError));
      }
    
      private handleError(err: HttpErrorResponse) {
        let errMsg = '';
    
        if (err.error instanceof ErrorEvent) {
          errMsg = `A client-side error occurred: ${err.error.message}`;
        } else {
          errMsg = `A server-side error occurred. Code: ${err.status}. Message: ${err.message}`;
        }
    
        return throwError(errMsg);
      }
    }
    

    We’ll send the form submission as HttpParams. A header for the ContentType should be included with the value application/x-www-form-urlencoded. The responseType option is specified as text because if successful, posting to the hidden form will return an HTML page containing a generic success message from Netlify. If you do not include this option, you will get an error because the response will be parsed as JSON. Below is a screenshot of the generic Netlify success message.

    forms/Netlify Generic Success Message
    Screenshot of Netlify Generic Success Message (Large preview)

    In the FeedbackComponent class, we shall import the NetlifyFormsService and Router. We’ll submit the form entry using the NetlifyFormsService.submitEntry method. If the submission is successful, we will redirect to the successful submission page and reset the form. We’ll use the Router service for the redirection. If unsuccessful, the errorMsg property will be assigned the error message and be displayed on the form.

    import { Router } from '@angular/router';
    import { NetlifyFormsService } from '../netlify-forms/netlify-forms.service';
    

    After that, inject both the NetlifyFormsService and Router in the constructor.

    constructor(
       private fb: FormBuilder,
       private router: Router,
       private netlifyForms: NetlifyFormsService
    ) {}
    

    Lastly, call the NetlifyFormsService.submitEntry method in FeedbackComponent.onSubmit.

    onSubmit() {
    this.netlifyForms.submitFeedbackEntry(this.feedbackForm.value).subscribe(
       () => {
         this.feedbackForm.reset();
         this.router.navigateByUrl('/success');
       },
       err => {
         this.errorMsg = err;
       }
     );
    }
    

    Create A Successful Submission Page

    When a user completes a submission, Netlify returns a generic success message shown in the last screenshot of the previous section. However, you can link back to your own custom success message page. You do this by adding the action attribute to the hidden HTML form. Its value is the relative path to your custom success page. This path must start with / and be relative to your root site.

    Setting a custom success page, however, does not seem to work when using a hidden HTML form. If the post request to the hidden HTML form is successful, it returns the generic Netlify success message as an HTML page. It does not redirect even when an action attribute is specified. So instead we shall navigate to the success message page after a submission using the Router service.

    First, let’s add content to the SuccessComponent we generated earlier. In success.component.html, add:

    <div id="container">
        <h1>Thank you!</h1>
        <hr>
        <p>Your feedback submission was successful.</p>
        <p>Thank you for sharing your thoughts with us!</p>
        <button routerLink="/">Give More Feedback</button>
    </div>
    

    To style the page, add this to success.component.css:

    p {
        margin: .2rem 0 0 0;
        text-align: center;
    }
    

    This is what the page looks like:

    Successful Submission Page
    Screenshot of Successful Submission Page (Large preview)

    In the FeedbackComponent class, we already added the Routerservice as an import and injected it into the constructor. In its onSubmitmethod, after the request is successful and the form has reset, we navigate to the successful submission page, /success. We use the navigateByUrl method of the router to do that.

    Creating The 404 Page

    The 404 page may not be necessary but is a nice to have. The contents of page-not-found.component.html would be:

    <div id="container">
        <h1>Page Not Found!</h1>
        <hr>
        <p>Sorry! The page does not exist.</p>
        <button routerLink="/">Go to Home</button>
    </div>
    

    To style it, add this to page-not-found.component.css:

    p {
        text-align: center;
    }
    

    This is what the 404 page will look like.

    404 Page
    Screenshot of 404 Page (Large preview)

    Fix Routing Before Deployment

    Since we’re using the Router service, all our routing is done on the client. If a link to a page in our app is pasted in the address bar (deep link) or there is a page refresh, that request we’ll be sent to our server. The server does not contain any of our routes because they were configured in the frontend, in our app. We’ll receive a 404 status in these instances.

    To fix this, we need to tell the Netlify server to redirect all requests to our index.html page. This way our Angular router can handle them. If you’re interested, you can read more about this phenomenon here and here.

    We’ll start by creating a _redirects file in our src folder. The _redirects file is a plain text file that specifies redirect and rewrite rules for the Netlify site. It should reside in the site publish site directory (dist/<app_name>). We’ll place it in the src folder and specify it as an asset in the angular.json file. When the app is compiled, it will be placed in dist/<app_name>.

    touch src/_redirects
    

    This file will contain the rule below. It indicates that all requests to the server should be redirected to index.html. We also add a HTTP status code option at the end to indicate that these redirects should return a 200 status. By default, a 301 status is returned.

    /*  /index.html 200
    

    The last thing we have to do is add the below option in our angular.json und er projects > {your_project_name} > architect > options > assets. Include it in the assets array:

    {
      "glob": "_redirects",
      "input": "src",
      "output": "/"
    }
    

    Preview Your App Locally

    Before you can deploy the feedback app, it’s best to preview it. This allows you to make sure your site works as you had intended it. You may unearth issues resulting from the build process like broken paths to resources among other things. First, you’ll have to build your app. We’ll then serve the compiled version using a server. We’ll use lite-server which is a lightweight live-reload server for web apps.

    Note: Since the app is not deployed on Netlify just yet, you’ll get a 404 error when you attempt to make the post request. This is because Netlify Forms only work on deployed apps. You’ll see an error on the form as shown in the screenshot below, however, it will work once you’ve deployed it.

    Error on Feedback Form
    Screenshot of Error on Feedback Form (Large preview)
    1. To begin, install lite-server:
      npm install lite-server --save-dev
      
    2. Next, within your app’s workspace directory, build your app. To make sure builds are run every time your files change, pass the --watch flag to it. Once the app is compiled, the results are written to the dist/<app name> output directory. If you are using a version control system, make sure to not check in the dist folder because it is generated and is only for preview purposes.
      ng build --watch
      
    3. To serve the compiled site, run the lite-server against the build output directory.
      lite-server --baseDir="dist/<app name>"
      

    The site is now served at localhost:3000. Check it out on your browser and make sure it works as expected before you begin its deployment.

    Deployment

    There are multiple ways you can deploy your Angular project onto Netlify Edge. We shall cover three here:

    1. Using netlify-builder,
    2. Using Git and the Netlify web UI,
    3. Using the Netlify CLI tool.

    1. Using netlify-builder

    netlify-builder facilitates the deployment of Angular apps through the Angular CLI. To use this method, your app needs to have been created using Angular CLI v8.3.0 or higher.

    1. From the Sites tab of your Netlify dashboard, create a new project. Since we won’t be using Git to create a project, drag any empty folder to the dotted-border area marked “Drag and drop your site folder here”. This will automatically create a project with a random name. You can change this name under the site’s domain settings later if you wish.
      Screenshot of the dashboard to create a project
      Screenshot of the dashboard to create a project (Large preview)

      This is what you should see once your project has been created.

      Screenshot of a project page for a sample project
      Screenshot of a project page for a sample project (Large preview)
    2. Before you can deploy using this method, you will need to get the Netlify project’s API ID and a Netlify personal access token from your account. You can get the project API ID from the site settings. Under Site Settings > General > Site Details > Site Information you will find your project’s API ID.
      Screenshot showing where the Site settings Button is
      Screenshot showing where the Site settings Button is (Large preview)
      Screenshot showing where the site’s API ID is in its settings
      Screenshot showing where the site’s API ID is in its settings (Large preview)

      You can get a personal access token in your user settings. At User Settings > Applications > Personal access tokens, click the New Access Token button. When prompted, enter the description of your token, then click the Generate Token button. Copy your token. For persistence’s sake, you can store these values in a .env file within your project but do not check this file in if you are using a version control system.

      Screenshot showing where the User Settings button is
      Screenshot showing where the User Settings button is (Large preview)
      Screenshot showing where to create a personal access token
      Screenshot showing where to create a personal access token (Large preview)
      Screenshot showing where to enter the token description
      Screenshot showing where to enter the token description (Large preview)
      Screenshot showing the token value
      Screenshot showing the token value (Large preview)
    3. Next, add netlify-builder to your project using ng add.
      ng add @netlify-builder/deploy
      

      Once it’s done installing, you will be prompted to add the API ID and personal access token.

      Screenshot showing the prompts from adding the netlify builder
      Screenshot showing the prompts from adding the netlify builder (Large preview)

      It’s optional to add these here. You could ignore this prompt because they will be added to your angular.json file which is usually checked in if you use a version control system. It’s not safe to store this kind of sensitive information on code repos. If you are not checking this file in, you could just input your API ID and personal access token. The entry below will be modified in your angular.json file under the architect settings.

      "deploy": {
          "builder": "@netlify-builder/deploy:deploy",
          "options": {
          "outputPath": "dist/<app name>",
          "netlifyToken": "",
          "siteId": ""
          }
      }
      
    4. All that’s left is to deploy your application by running:
      NETLIFY_TOKEN=<access token> NETLIFY_API_ID=<api id> ng deploy
      

      Alternatively, you could put this in a script and run it when you need to deploy your app.

      # To create the script
      touch deploy.sh && echo "NETLIFY_TOKEN=<access token> NETLIFY_API_ID=<api id> ng deploy" >> deploy.sh && chmod +x deploy.sh
      
      # To deploy
      ./deploy.sh
      

      This is the output you should see once you run this command:

      Screenshot showing the results of the deployment
      Screenshot showing the results of the deployment (Large preview)

    2. Using Git And The Netlify Web UI

    If your Angular app’s code is hosted on either Github, Bitbucket, or Gitlab, you can host the project using Netlify’s web UI.

    1. From the Sites tab on your Netlify dashboard, click the “New site from Git” button.
      Screenshot showing the button to create a new site
      Screenshot showing the button to create a new site (Large preview)

    2. Connect to a code repository service. Pick the service where your app code is hosted. You’ll be prompted to authorize Netlify to view your repositories. This will differ from service to service.
      Screenshot showing options for connecting to a Git provider
      Screenshot showing options for connecting to a Git provider(Large preview)
    3. Pick your code repository.
      Screenshot showing list of available repositories
      Screenshot showing list of available repositories (Large preview)
    4. Next, you’ll specify the deployments and build settings. In this case, select the branch you’d like to deploy from, specify the build command as ng deploy --prod and the publish directory as dist/<your app name>.
      Screenshot showing build and deployment settings
      Screenshot showing build and deployment settings (Large preview)
    5. Click the Deploy Site button and you’re done.

    3. Using The Netlify CLI Tool

    1. To start, install the Netlify CLI tool as follows:
      npm install netlify-cli -g
      

      If the installation is successful, you should see these results on your terminal:

      Screenshot showing results of a successful Netlify CLI installation
      Screenshot showing results of a successful Netlify CLI installation (Large preview)
    2. Next, log in to Netlify by running:
      netlify login
      

      When you run this command, it will navigate to a browser window where you will be prompted to authorize the Netlify CLI. Click the Authorize button. You can then proceed to close the tab once authorization is granted.

      Screenshot showing a dialog requesting authorization of Netlify CLI
      Screenshot showing a dialog requesting authorization of Netlify CLI (Large preview)
      Screenshot showing authorization granted dialog
      Screenshot showing authorization granted dialog (Large preview)
    3. To create a new Netlify project, run the following on your terminal:
      netlify init
      

      You will be prompted to either connect your Angular app to an existing Netlify project or create a new one. Choose the Create & configure a new site option.

      Screenshot showing options for creating or connecting a project
      Screenshot showing options for creating or connecting a project (Large preview)

      Next, select your team and a name for the site you would like to deploy. Once the project has been created, the CLI tool will list site details for your project.

      Screenshot showing the details for the new site
      Screenshot showing the details for the new site (Large preview)

      After which the CLI tool will prompt you to connect your Netlify account to a Git hosting provider to configure webhooks and deploy keys. You cannot opt-out of this. Pick an option to login in then authorize Netlify.

      Screenshot showing prompt to connect to a Git provider
      Screenshot showing prompt to connect to a Git provider (Large preview)

      Next, you’ll be asked to enter a build command. Use:

      ng build --prod
      

      Afterward, you’ll be asked to provide a directory to deploy. Enter dist/<app name> with your app’s name.

      Screenshot showing a build-settings prompt
      Screenshot showing a build-settings prompt (Large preview)

      At the end of that, the command will complete and display this output.

      Screenshot showing results of a successful project initialization
      Screenshot showing results of a successful project initialization (Large preview)
    4. To deploy the app, run:
      netlify deploy --prod
      

      Using the --prod flag ensures that the build is deployed to production. If you omit this flag, the netlify deploy command will deploy your build to a unique draft URL that is used for testing and previewing. Once the deployment is complete, you should see this output:

      Screenshot showing results of a successful deployment
      Screenshot showing results of a successful deployment (Large preview)

    Viewing Form Submissions

    Form submissions can be viewed on the Netlify dashboard under the Forms tab of your site. You can find it at app.netlify.com/sites/<your_site_name>/forms. On this page, all your active forms will be listed. The name attribute that you put down in the hidden form element is the name of the form on the dashboard.

    Once you select a form, all the submissions for that form will be listed. You can choose to download all the entries as a CSV file, mark them as spam, or delete them.

    Active forms list
    Screenshot of active forms listed on the site dashboard (Large preview)
    Form Entries
    Screenshot of forms entries listed on the form dashboard (Large preview)

    Conclusion

    Netlify Forms allow you to collect form submission from your app without having to create or configure a backend to do it. This can be useful especially in apps that only need to collect a limited amount of data like contact information, customer feedback, event sign-ups, and so on.

    Pairing Angular reactive forms with Netlify forms allow you to structure your data model. Angular reactive forms have the added benefit of having their data model and form elements being in sync with each other. They do not rely on UI rendering.

    Although Netlify Forms only work when deployed on Netlify Edge, the hosting platform is pretty robust, provides useful features like A/B testing, and automates app builds and deployments.

    You can continue reading more about using Netlify with your forms over here.

    Smashing Editorial
    (ra, il)

    Source link

    web design

    React Form Validation With Formik And Yup — Smashing Magazine

    10/12/2020

    About The Author

    Nefe is a Frontend Developer who enjoys learning new things and sharing his knowledge with others.
    More about
    Nefe

    Forms are an integral part of how users interact with our websites and web applications. Validating the data the user passes through the form is a critical aspect of our jobs as web developers. However, it doesn’t have to be a pain-staking process. In this article, we’ll learn how Formik handles the state of the form data, validates the data, and handles form submission.

    As developers, it is our job to ensure that when users interact with the forms we set up, the data they send across is in the form we expect.

    In this article, we will learn how to handle form validation and track the state of forms without the aid of a form library.
    Next, we will see how the Formik library works. We’ll learn how it can be used incrementally with HTML input fields and custom validation rules. Then we will set up form validation using Yup and Formik’s custom components and understand how Yup works well with Formik in handling Form validation. We will implement these form validation methods to validate a simple sign up form I have set up.

    Note: This article requires a basic understanding of React.

    Form Validation In React

    On its own, React is powerful enough for us to be able to set up custom validation for our forms. Let’s see how to do that. We’ll start by creating our form component with initial state values. The following sandbox holds the code for our form:

    Form validation without the use of a library

    const Form = () => {
      const intialValues = { email: "", password: "" };
      const [formValues, setFormValues] = useState(intialValues);
      const [formErrors, setFormErrors] = useState({});
      const [isSubmitting, setIsSubmitting] = useState(false);
    }

    With the useState hook, we set state variables for the formValues, formErrors and isSubmitting.

    • The formValues variable holds the data the user puts into the input fields.
    • The formErrors variable holds the errors for each input field.
    • The isSubmitting variable is a boolean that tracks if the form is being submitted or not. This will be true only when there are no errors in the form.
    const submitForm = () => {
        console.log(formValues);
      };
    
     const handleChange = (e) => {
        const { name, value } = e.target;
        setFormValues({ ...formValues, [name]: value });
      };
    
    const handleSubmit = (e) => {
        e.preventDefault();
        setFormErrors(validate(formValues));
        setIsSubmitting(true);
      };
    
    const validate = (values) => {
        let errors = {};
        const regex = /^[^s@]+@[^s@]+.[^s@]{2,}$/i;
        if (!values.email) {
          errors.email = "Cannot be blank";
        } else if (!regex.test(values.email)) {
          errors.email = "Invalid email format";
        }
        if (!values.password) {
          errors.password = "Cannot be blank";
        } else if (values.password.length < 4) {
          errors.password = "Password must be more than 4 characters";
        }
        return errors;
      };
    
    useEffect(() => {
        if (Object.keys(formErrors).length === 0 && isSubmitting) {
          submitForm();
        }
      }, [formErrors]);

    Here, we have 4 form handlers and a useEffect set up to handle the functionality of our form.

    • handleChange
      This keeps the inputs in sync with the formValues state and updates the state as the user types.
    • validate
      We pass in the formValues object as a argument to this function, then based on the email and password meeting the validation tests, the errors object is populated and returned.
    • handleSubmit
      Whenever the form is submitted, the formErrors state variable is populated with whatever errors may exist using the setFormErrors(validate(formValues)) method.
    • useEffect
      Here, we check if the formErrors object is empty, and if isSubmitting is true. If this check holds true, then the submitForm() helper is called. It has single dependency, which is the formErrors object. This means it only runs when the formErrors object changes.
    • submitForm: this handles the submission of the form data.
    return (
        <div className="container">
          <h1>Sign in to continue</h1>
          {Object.keys(formErrors).length === 0 && isSubmitting && (
            <span className="success-msg">Signed in successfully</span>
          )}
          <form onSubmit={handleSubmit} noValidate>
            <div className="form-row">
              <label htmlFor="email">Email</label>
              <input
                type="email"
                name="email"
                id="email"
                value={formValues.email}
                onChange={handleChange}
                className={formErrors.email && "input-error"}
              />
              {formErrors.email && (
                <span className="error">{formErrors.email}</span>
              )}
            </div>
            <div className="form-row">
              <label htmlFor="password">Password</label>
              <input
                type="password"
                name="password"
                id="password"
                value={formValues.password}
                onChange={handleChange}
                className={formErrors.password && "input-error"}
              />
              {formErrors.password && (
                <span className="error">{formErrors.password}</span>
              )}
            </div>
            <button type="submit">Sign In</button>
          </form>
        </div>
      );

    Here, we pass in the handleChange helper functions to the inputs’ onChange attribute. We link the value of the inputs to the formValues object, making them controlled inputs. From the React docs, controlled inputs are inputs whose values are controlled by React. An input-error style is applied if there are any errors related to that specific input field. An error message is conditionally displayed beneath each input if there are any errors related to that specific input field. Finally, we check if there are any errors in the errors object and if isSubmitting is true. If these conditions hold true, then we display a message notifying the user that they signed in successfully.

    With this, we have a fully functional and validated form set up without the aid of a library. However, a form library like Formik with the aid of Yup can simplify the complexities of handling forms for us.

    What Are Formik And Yup?

    Right from the docs:

    “Formik is a small library that helps you with the 3 most annoying parts in handling forms:

    1. Getting values in and out of form state.
    2. Validation and error messages
    3. Handling form submission.

    Formik is a flexible library. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use. It can be used with HTML input fields and custom validation rules, or Yup and the custom components it provides. Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React.

    Yup is a JavaScript object schema validator. While it has many powerful features, we’ll focus on how it helps us create custom validation rules so we don’t have to. This is a sample Yup object schema for a sign-up form. We’ll go into Yup and how it works in depth later in the article.

    const SignUpSchema = Yup.object().shape({
      firstName: Yup.string()
        .min(2, "Too Short!")
        .max(50, "Too Long!")
        .required("Firstname is required"),
    
      lastName: Yup.string()
        .min(2, "Too Short!")
        .max(50, "Too Long!")
        .required("Lastname is required"),
    
      phoneNumber: Yup.string()
        .required("Phone number is required")
        .matches(
    /^([0]{1}|+?[234]{3})([7-9]{1})([0|1]{1})([d]{1})([d]{7})$/g,
          "Invalid phone number"
        ),
    
      email: Yup.string().email().required("Email is required"),
    
      password: Yup.string()
        .required("Password is required")
        .min(6, "Password is too short - should be 6 chars minimum"),
    });

    Formik, HTML Input Fields And Custom Validation Rules

    The following sandbox holds the code for this form set up:

    The first thing we have to do is install Formik.

    npm i formik

    Then we can go ahead to import it in the file where we’ll make use of it.

    import { Formik } from "formik";

    Before creating the component, we need to create an initialValues and validate object which we’ll pass as props to the Formik component when we set it up. initialValues and validate are code snippets, not normal words.

    The decision to do this outside the component is not a technical one, but rather for readability of our code.

    const initialValues = {
      email: "",
      password: ""
    };

    initialValues: is an object that describes the initial values of the respective form fields. The name given to each key in the initialValues must correspond with the value of the name of the input field we want Formik to watch.

    const validate = (values) => {
      let errors = {};
      const regex = /^[^s@]+@[^s@]+.[^s@]{2,}$/i;
      if (!values.email) {
        errors.email = "Email is required";
      } else if (!regex.test(values.email)) {
        errors.email = "Invalid Email";
      }
      if (!values.password) {
        errors.password = "Password is required";
      } else if (values.password.length < 4) {
        errors.password = "Password too short";
      }
      return errors;
    };

    validate: this accepts a function that handles the form validation. The function accepts an object in the form of data values as an argument and validates each property in the object based on the rules defined. Each key in the values object must correspond with the name of the input field.

    const submitForm = (values) => {
      console.log(values);
    };

    onSubmit: This handles what happens after the user submits. The onSubmit prop takes a callback function that will only run when there are no errors, meaning the user inputs are valid.

    const SignInForm = () => {
      return (
        <Formik
          initialValues={initialValues}
          validate={validate}
          onSubmit={submitForm}
        >
          {(formik) => {
            const {
              values,
              handleChange,
              handleSubmit,
              errors,
              touched,
              handleBlur,
              isValid,
              dirty
            } = formik;
            return (
                <div className="container">
                  <h1>Sign in to continue</h1>
                  <form onSubmit={handleSubmit}>
                    <div className="form-row">
                      <label htmlFor="email">Email</label>
                      <input
                        type="email"
                        name="email"
                        id="email"
                        value={values.email}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        className={errors.email && touched.email ? 
                        "input-error" : null}
                      />
                      {errors.email && touched.email && (
                        <span className="error">{errors.email}</span>
                      )}
                    </div>
    
                    <div className="form-row">
                      <label htmlFor="password">Password</label>
                      <input
                        type="password"
                        name="password"
                        id="password"
                        value={values.password}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        className={errors.password && touched.password ? 
                         "input-error" : null}
                      />
                      {errors.password && touched.password && (
                        <span className="error">{errors.password}</span>
                      )}
                    </div>
    
                    <button
                      type="submit"
                      className={dirty && isValid ? "" : "disabled-btn"}
                      disabled={!(dirty && isValid)}>
                      Sign In
                    </button>
                  </form>
                </div>
            );
          }}
        </Formik>
      );
    };

    We pass in the initialValues object, and the submitForm and validate functions we defined earlier into Formik’s initialValues, onSubmit and validate props respectively.

    Using the render props pattern, we have access to even more props the Formik API provides.

    1. values
      This holds the values of the user inputs.
    2. handleChange
      This is the input change event handler. It is passed to the input field <input onChange={handleChange}>. It handles the changes of the user inputs.
    3. handleSubmit
      The form submission handler. It is passed into the form <form onSubmit={props.handleSubmit}>. This fires the function passed into the onSubmit prop whenever the form is submitted.
    4. errors
      This object holds the validation errors that correspond to each input field, and is populated with the definitions we passed into the Yup object schema.
    5. touched
      This is an object that watches if a form field has been touched. Each key corresponds to the name of the input elements and has a boolean value.
    6. handleBlur
      This is the onBlur event handler, and it is passed to the input field <input onBlur={handleBlur} />. When the user removes focus from an input, this function is called. Without it, if there are any errors in the input when it loses focus, the errors will only display when the user tries to submit.
    7. isValid
      Returns true if there are no errors (i.e. the errors object is empty) and false otherwise.
    8. dirty
      This prop checks if our form has been touched or not. We can use this to disable our submit button when the form loads initially.

    When the form is submitted, Formik checks if there are any errors in the errors object. If there are, it aborts the submission and displays the errors. To display the span using HTML inputs, we conditionally render and style the error message of each respective input field if the field has been touched and there are errors for that field.

    <button
      type="submit"
      className={!(dirty && isValid) ? "disabled-btn" : ""}
      disabled={!(dirty && isValid)}>
          Sign In
    </button>

    Also, we can add a visual cue to the button. The button is conditionally styled and disable it if there are errors in the errors object using the isValid and the dirty props.

    Validation Using Formik’s Components And Yup

    This sandbox holds the final code for this setup.

    npm i yup
    import { Formik, Form, Field, ErrorMessage } from "formik";
    import * as Yup from "yup";

    We install Yup, import the Field, Form, and the ErrorMessage components from Formik.

    Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React. With that we can then go ahead to create the schema we’ll be using for the sign in form using Yup. Instead of creating custom validations for each possible input field, which can be tedious, depending on the number of fields there are, we can leave that to Yup to handle.

    const SignInSchema = Yup.object().shape({
      email: Yup.string().email().required("Email is required"),
    
      password: Yup.string()
        .required("Password is required")
        .min(4, "Password is too short - should be 4 chars minimum"),
    });

    Yup works similarly to how we define propTypes in React. We created an object schema with Yup’s object function. We define the shape of the validation object schema and pass it into Yup’s shape() method. The required() method. This method takes a string as an argument, and this string will be the error message. that displays whenever a required field is left blank.

    This schema has two properties:

    • An email property that is a string type and is required.
    • A password property that is of number type but is not required.

    We can chain validation is Yup as seen above. The properties of the schema object match the name of the input fields. The docs go into the different validation methods available in Yup.

    const SignInForm = () => {
      return (
        <Formik
          initialValues={initialValues}
          validationSchema={signInSchema}
          onSubmit={(values) => {
            console.log(values);
          }}
        >
          {(formik) => {
            const { errors, touched, isValid, dirty } = formik;
            return (
              <div className="container">
                <h1>Sign in to continue</h1>
                <Form>
                  <div className="form-row">
                    <label htmlFor="email">Email</label>
                    <Field
                      type="email"
                      name="email"
                      id="email"
                      className={errors.email && touched.email ? 
                      "input-error" : null}
                    />
                    <ErrorMessage name="email" component="span" className="error" />
                  </div>
    
                  <div className="form-row">
                    <label htmlFor="password">Password</label>
                    <Field
                      type="password"
                      name="password"
                      id="password"
                      className={errors.password && touched.password ? 
                      "input-error" : null}
                    />
                    <ErrorMessage
                      name="password"
                      component="span"
                      className="error"
                    />
                  </div>
    
                  <button
                    type="submit"
                    className={!(dirty && isValid) ? "disabled-btn" : ""}
                    disabled={!(dirty && isValid)}
                  >
                    Sign In
                  </button>
                </Form>
              </div>
            );
          }}
        </Formik>
      );
    };

    While using HTML input fields get the job done, Formik’s custom components make things even easier for us, and reduce the amount of code we have to write! What are these custom components Formik provides us?

    1. Formik
      We’ve been using this for a while now. This is required for the other components to be usable.
    2. Form
      A wrapper that wraps the HTML <form/> element. It automatically links the onSubmit method to the form’s submit event.
    3. Field
      In the background, this automatically links the form input’s onChange, onBlur and value attributes to Formik’s handleChange, handleBlur, and values object respectively. It uses the name prop to match up with the state and automatically keeps the state in sync with the input value. With this component, we can decide to display it as an input field we want using it’s as property. For example, will render a textarea. By default, it renders an HTML input field.
    4. ErrorMessage
      It handles rendering the error message for its respective field based on the value given to the name prop, which corresponds to the <Field />’s name prop. It displays the error message if the field has been visited and the error exists. By default, it renders a string is the component prop is not specified.

    We pass the signInSchema into Formik using the validationSchema prop. The Formik team loves the Yup validation library so they created a specific prop for Yup called validationSchema which transforms errors into objects and matches against their values and touched functions.

    Conclusion

    Users do not know or care how you handle form validation. However, for you the developer, it should be as painless a process as possible, and I believe Formik stands out as a solid choice in that regard.

    We have successfully looked at some of the options available to us when validating forms in React. We have seen how Formik can be used incrementally, and how it pairs well with Yup in handling form validation.

    Resources

    Smashing Editorial
    (ks, ra, yk, il)

    Source link

    web design

    Publicity Becomes An Art Form — Smashing Magazine

    06/26/2020

    In the tenth issue of Inspired Design Decisions, Andy Clarke will explain how Giovanni Pintori — the Italian graphic designer best known for his work with Olivetti — can inspire design for the web with his distinctive use of color and shape. Andy will teach you how to use color to attract attention and then to lead someone’s eye around a design. He’ll discuss how a minimal color palette can act as a guide, helping people through a design, and how lines and shapes add structure and style.

    With one or two occasional exceptions, I’ve spent the past twenty-two years designing for countless clients. A few of these projects lasted a year, some several months, but the majority for no more than a few weeks.

    Being completely absorbed for a few weeks or months in designing a product interface or a website can be a thrill. It often starts with the gratification which comes from winning the work. Gaining a new client’s confidence and trust can be addictive. During the seductive “getting to know you” phase, you learn about the client and what they expect from you and your work. Addictive personalities like mine crave the intensity of these feelings, but — just like some relationships — the initial excitement soon fades into the realities of working together.

    This creative promiscuity has suited my often short attention span and restless curiosity very well. But, there were times I wished I could stay with an organization for longer, get to know them better, and be a positive influence on what they do and make.

    I know many designers who work in-house. While I never envy their commute or the money they spend on living close to work, there’s a part of me that envies their ability to stay and shape the long-term creative direction of a company in the way which Giovanni Pintori helped Olivetti.

    “In our day and age, publicity has become an art form, and increasingly needs to live up to this name. Publicity is a form of discourse that should eschew vagueness in favor of brevity, clarity, and persuasiveness. Those who engage in publicity (writers, painters, architects) need logic and imagination in equal measure.”

    — Giovanni Pintori

    Italian designer Giovanni Pintori worked for business products manufacturer Olivetti for over 31 years. During this time, his style developed into the company’s unique design vocabulary. The appeal of working with one company for longer than a few months has become stronger as I’ve got older. For the past 18 months, I’ve devoted most of my time to working with a Swiss cybersecurity company, based not far from Milan and where Giovanni Pintori called home.

    Like Olivetti, this company values design in every form. While my top priority is the design of the company’s products, I’ve also had the opportunity to influence their brand, marketing, and overall creative direction.

    I still spend time on other people’s projects when the work attracts me, but I’ve learned how rewarding a long-term client relationship can be. I’m happy and more creatively satisfied than I have been in years. Plus, as old age catches up with me, I don’t have the energy to chase every attractive project like I used to.

    Read More From The Series

    Inspired By Giovanni Pintori

    Born in Sardinia in 1912, Giovanni Pintori became one of the most influential European graphic designers of the 20th century. He became known for the distinctive style he crafted into Olivetti’s design language for over 30 years.

    Pintori studied design at Italy’s influential L’Istituto Superiore per le Industrie Artistiche (Higher Institute for Artistic Industries) where he was surrounded by the creative arts. ISIA was a progressive school where students studied ceramics, painting, metalwork, and woodwork.

    While studying at ISIA, Pintori met Renato Zveteremich the advertising director and publicist who headed Olivetti’s advertising department during the 1930s. After graduating from HIAI, Pintori joined Olivetti to work under Zveteremich and became the company’s art director in 1950.

    poster by Giovanni Pintori and etching by Joan Miró
    Left: Tir à l’arc (1972) etching and aquatint by Joan Miró. Right: Olivetti Lettera 22 poster (1954) designed by Giovanni Pintori. (Large preview)

    Olivetti manufactured business machines, most famously its range of typewriters. When Pintori joined Olivetti, the company was already known for its original product designs. Its products were immediately recognizable, and under the guidance of industrial designer Marcello Nizzoli, every detail of their designs—from the shape of a spacebar to the color of their outer casings was carefully considered.

    “If artists are called upon to interpret, express, and defend the functional purity of a machine, it is truly a sign that the machine has entered the human spirit and that the problem of forms and relationships is still of an intuitive nature.”

    — Renato Zveteremich

    But Olivetti’s preoccupation with design didn’t end with its products. Creativity was an essential part of the company’s culture which was evident from the architecture of its factories and offices to its advertising and graphic design used to promote its products.

    Over his 30 year career at Olivetti, Pintori designed the company’s advertising, brochures, and even their annual calendars. Pintori’s aesthetic style was bold and confident. He used bright colors from minimal color palettes and combined them with shapes to fill his designs with energy.

    But Pintori’s work wasn’t just playful, it was thoughtful. His choice of shapes wasn’t abstract. Shapes suggested the benefits of using a product rather than describe its features literally. Pintori didn’t just illustrate products, he brought them to life through his designs by suggesting how they might be used and what they could do to enhance people’s lives and work.

    “I do not attempt to speak on behalf of the machines. Instead, I have tried to make them speak for themselves, through the graphic presentation of their elements, their operations and their use.”

    Pintori defined Olivetti’s image far beyond his time at the company, and he continued to work on projects with them after leaving in 1967. He established his own studio in Milan, where he worked as a freelance designer, before retiring and dedicating himself to painting.

    painting by Jasper Johns and poster by Giovanni Pintori
    Left: Numbers in Color (1958–59) painting by Jasper Johns. Right: Olivetti numbers poster (1949) designed by Giovanni Pintori. (Large preview)

    Giovanni Pintori died in Milan in 1999, and there’s a book, Pintori by Marta Sironi and published by Moleskine which catalogs his astonishing career.

    Pintori’s work inspires not only because of the boldness of his colorful shapes, but because of what they represent. Pintori understood that promoting a product required more than listing its features. Publicity should tell a story that resonates with customers, and that’s a lesson we should all be inspired by.

    brochures designed by Giovanni Pintori
    Left: Olivetti Lettera 22 brochure designed by Giovanni Pintori, 1954. Right: Olivetti Lettera 22 brochure designed by Giovanni Pintori, 1965. (Large preview)
    posters designed by Giovanni Pintori
    Left: Olivetti Graphika poster designed by Giovanni Pintori, 1956. Right: Olivetti Tetractys poster designed by Giovanni Pintori, 1956. (Large preview)

    Creating Color Palettes

    The colors we choose should tell a story about a company, product, or service as eloquently as our layout or typography. Our color choices can attract someone’s attention, influence their perception of what we do, and even stimulate emotions. Color plays an essential role in making a product or website easy and intuitive to use. As well as brand colors, color palettes for the web help people to navigate, let them know what they can press, and where they’ve been.

    I like to keep my colors simple, and my palettes rarely contain more than three hues; a dominant color, secondary or supporting color, and an accent. To these, I add a small selection of neutral colors for use as backgrounds, borders, and text.

    To add depth to my designs — and to give me greater flexibility — I also introduce shades and tints of each of my hues. I use darker shades for borders — for example — around buttons — and lighter tints to add highlights.

    Left: My color palette. Right: Demonstrating percentage use.
    Left: My color palette. Right: Demonstrating percentage use. (Large preview)

    Since operating system dark modes have become more prevalent, I also subtly alter the lightness and saturation of colors in my palettes, so they appear more vibrant against dark backgrounds.

    color palette against a dark background
    Left & right: Presenting a color palette against a dark background. (Large preview)

    Using Primary Colors

    Pintori-inspired design
    The primary colors used in this Pintori-inspired design emphasise the clarity of its message and the simplicity of its layout. (Large preview)

    The HTML needed to implement my first Pintori-inspired design is meaningful and simple as the design itself. I need just four structural elements; a header which contains two SVGs of the iconic Morris Traveller’s profile, the main element for my running text, an SVG of the Traveller’s front, and finally a footer which contains the Morris Motors company logo:

    <header>  
      <svg>…</svg>
      <svg>…</svg>
    </header>
    
    <main>
      <h1>…</h1>
      <p>…</p>
    </main>
    
    <figure>
      <svg>…</svg>
    </figure>
    
    <footer>
      <svg>…</svg>
    </footer>
    

    While external SVG files will be cached and ready to render, I now embed SVG in my HTML whenever possible. Fewer external files mean fewer HTTP requests, but the benefits of embedding go far beyond performance.

    Subtle changes in color saturation and lightness between light themes and dark modes are often necessary to maintain the punchiness of design elements against contrasting background colors. When an SVG is embedded in HTML, its fills and strokes can be subtlety altered using CSS.

    I start by applying color and typography foundation styles for the distinguished dark version of my design. These include Moderna Sans, a versatile sans-serif typeface designed by Luciano Vergara and Alfonso García which I chose to evoke the style of Pintori’s work for Olivetti:

    body {
    padding: 2rem;
    background-color: #262626;
    font-family: "moderna_sans-light";
    color: #fff; }
    
    h1 {
    font-family: "moderna_sans-bold-cnd-it";
    font-size: 2.8rem;
    font-weight: normal;
    line-height: 1; }
    

    Flexbox transforms my header into a horizontally scrolling panel, one of the most effective ways to maintain visual hierarchy in a small screen design:

    header {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: scroll; }
    

    The flex-grow property with its value of 1 ensures all images expand to fill any available space, while flex-basis makes sure these flex items start from a minimum of 640px;

    header svg {
    flex-grow: 1;
    flex-basis: 640px; }
    header svg:not(:last-of-type) {
    margin-right: 2rem; }
    

    Finally, I add large amounts of horizontal padding and align the Morris logo to the centre of my footer:

    footer {
    padding-right: 8rem;
    padding-left: 8rem;
    text-align: center; }
    

    My horizontal scrolling panel adds interest to a small screen, but the extra space available on medium-size screens allows me to show more of my quintessentially English Travellers.

    CSS Grid offers the precise placement and stacking of elements which Flexbox lacks and is the perfect choice for this header on medium to large screens. I change the display property’s value from flex to grid, then add three symmetrical columns and rows.

    While the width of the two outer columns is fixed at 270px, the inner column expands to fill all remaining space. I use a similar technique for the three rows, fixing the outer two at a height of 100px. This offsets the position of both images and adds depth to this design:

    header {
    display: grid;
    grid-template-columns: 270px 1fr 270px;
    grid-template-rows: 100px 1fr 100px; }
    

    Using pseudo-class selectors and line numbers, I place the first SVG, then reduce it in size to add perspective:

    header svg:first-of-type {
    grid-column: 2 / 4;
    grid-row: 1 / 2;
    transform: scale(.85); }
    

    Then, I place the second of my two graphics. I raise it within the stacking order by adding a higher z-index value which brings it visually closer to the viewer:

    header svg:last-of-type {
    grid-column: 1 / 3;
    grid-row: 2 / 4;
    z-index: 2; }
    

    Even a seemingly mundane even-ratio grid can result in an original layout when a design includes plenty of whitespace to help lead the eye. For this medium-size design, I apply a symmetrical six-column grid with column and rows gap values which are proportional to the width and height of a screen:

    @media (min-width: 48em) {
    body {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    column-gap: 2vw;
    row-gap: 2vh; }
    }
    

    My header element fills the entire width of my grid. Then, I place the main, figure, and footer elements, adding proportionally more white space to narrow the width of my figure and footer:

    header {
    grid-column: 1 / -1; }
    
    main {
    grid-column: 2 / 6; }
    
    figure {
    grid-column: 3 / 5; }
    
    footer {
    grid-column: 3 / 5;
    padding-right: 4rem;
    padding-left: 4rem; }
    

    This design becomes more distinguished with the space available on large screens.

    For them, I apply grid values to the body element to create the eight columns of a 6+4 compound grid:

    @media (min-width: 64em) {
    
    body {
    grid-template-columns: 2fr 1fr 1fr 2fr 2fr 1fr 1fr 2fr; }
    }
    

    Basing my medium-size design on six columns, then including the same grid in my large screen compound, helps to maintain proportions throughout all sizes of my design. Then, I reposition the four structural elements onto my new grid:

    header {
    grid-column: 1 / 8; }
    
    main {
    grid-column: 2 / 5;
    text-align: right; }
    
    figure {
    grid-column: 5 / 7; }
    
    footer {
    grid-column: 4;
    padding: 0; }
    

    Finally, to create a solid block of content in the centre of my design, I bind the main content to its now adjacent figure by realigning its text to the right:

    main {
    text-align: right; }
    
    Primary colors
    Left: Primary colors against an off-white background. Right: Primary colors stand out against this dark background. (Large preview)
    The monochromatic color palettes used in my next design. (Large preview)

    Monochromatic Palettes

    Even after over twenty years in the business, I still find working with color the most challenging aspect of design. Perhaps that’s why I so often gravitate towards monochromatic color schemes because they make achieving a visually cohesive look quite simple.

    Monochromatic color palettes contain variations in shade, tints, and tones, by adding varying percentages of black, grey, or white to a chosen base color.

    • Shades: Darken color using black
    • Tints: Lighten color using white
    • Tones: Desaturate color using grey

    When they’re used for backgrounds, borders, and details, shades and tints can make a design feel harmonious.

    Using shades, tints, and tones can help to tone down vibrant colors which might draw unwanted attention to aspects of a design. They are particularly useful when developing a more varied color palette from a set of existing brand colors.

    I often choose either a purely monochromatic or partially-monochromatic palette which includes an accent color. This added color acts as a counterpoint to the base color and gives a design greater depth.

    tints and tones
    Left: Shades: 100%–50% Center: Tints: 100%–50% Right: Tones: 100%–50%. (Large preview)

    Limiting The Palette

    full-color version of a Pintori-inspired design
    This full-color version of my Pintori-inspired design includes several monochromatic elements placed onto a modular grid. (Large preview)

    Thanks to CSS Grid, background image gradients, and pseudo elements, this next Pintori-inspired design achieves enormous value from a very small set of HTML elements. I need only a headline, a single paragraph, plus seven empty divisions. I give each division its own identity. This allows me to give them their own distinctive style:

    <h1>…</h1>
    <p>…</p>
    <div id="panel-a"></div>
    <div id="panel-b"></div>
    <div id="panel-c"></div>
    <div id="panel-d"></div>
    <div id="panel-e"></div>
    <div id="panel-f"></div>
    <div id="panel-g"></div>
    

    This HTML places the headline and paragraph before the seven panels, but look closely at the finished small screen design, and you’ll see this content has been reordered to place the Morris logo, then a picture of the Traveller’s front-end at the top.

    Whereas I often introduce grid properties to medium and large screens, CSS Grid is also useful for reordering content on smaller screens. For this design, I change the body element’s display value to grid, then introduce a viewport height-based gap between the intrinsic, unspecified rows:

    body {
    display: grid;
    row-gap: 2vh; }
    

    Then, I reorder the panels which contain my Morris Motors logo and image, plus the headline, using row line numbers:

    #panel-d { grid-row: 1; }
    #panel-e { grid-row: 2; }
    h1 { grid-row: 3; }
    

    Because my panel divisions have no other elements, their height will collapse to zero, leaving only their borders. To ensure there’s space to display their generated backgrounds and content, I specify a minimum height for all panels:

    [id*="panel"] {
    min-height: 380px; }
    

    The panel which appears first in my small screen design shows the Morris Motors logo, which I insert using a CSS generated content data URI. If you’re unfamiliar with this handy content type, a data URI is a file which has been encoded into a string. You can use a data URI anywhere in your CSS or HTML:

    <img src="data:image/png…">
    <img src="data:image/svg+xml…">
    
    div { 
    background-image: url("data:image/svg+xml…"); }
    

    When a browser finds a data URI, it decodes the content and reconstructs the original file. Data URIs aren’t limited to encoded images but are frequently used to encode PNG format images and SVGs. You will find several tools for converting images to data URIs online.

    First, I change the minimum height of this panel to match the height of my logo, then I insert the logo:

    #panel-d {
    min-height: 90px;
    text-align: center; }
    
    #panel-d:before {
    content: url("data:image/svg+xml…");
    display: block;
    width: 135px;
    height: 90px;
    margin: 0 auto; }
    

    I use a similar technique to place a background image behind my paragraph. I add repeat, position, and size properties which make the background flexible and place it always at the horizontal and vertical centre of my paragraph:

    p {
    background-image: url("data:image/svg+xml…"); }
    
    p {
    background-repeat: no-repeat;
    background-position: 50% 50%;
    background-size: 50% 50%; }
    
    panels with distinctive graphic design
    Each one of the panels has its own distinctive graphic design. (Large preview)

    Each one of my panels has its own distinctive graphic design. I could’ve placed images into these seven panels, but this would’ve required at least seven additional HTTP requests. So instead, I use various combinations of multiple background images using data URIs and CSS gradients to achieve the results I need.

    The first panel contains a graphic of the Morris’s hub cap over a striped blue, white, and black background. The hub cap background image comes from a data URI:

    #panel-a {
    background-image: url("data:image/svg+xml…"); }
    

    Then, I add the second, striped background image using a linear-gradient:

    #panel-a {
    background-image: url("data:image/svg+xml…"),
    linear-gradient(
    to bottom, 
    #34749F,
    #34749F 65px,
    #fff 65px,
    #fff 80px,
    #262626 80px); }
    

    I specify two sets of comma separated repeat, position, and size values, remembering to keep them in the same order as my background images:

    #panel-a {
    background-repeat: no-repeat, repeat-x;
    background-position: 50% 100%, 0 0;
    background-size: 75% 75%, auto auto; }
    

    This next panel includes two SVG images, followed by more complex black, yellow, and white stripes. By placing color stops with different colors at the same position in my gradient, I create a striped background with hard lines between my colors:

    #panel-b {
    background-image: 
    url("data:image/svg+xml…"),
    url("data:image/svg+xml…"),
    linear-gradient(
    to bottom, 
    #B5964D,
    #B5964D 125px,
    #262626 125px,
    #262626 140px,
    #fff 140px,
    #fff 155px,
    #262626 155px); }
    
    #panel-b {
    background-repeat: no-repeat, no-repeat, repeat-x;
    background-position: 50% 45px, 50% 190px, 0 0;
    background-size: 90%, 90%, auto; }
    

    I developed each of my panels using different combinations of these same techniques, making them fast loading and flexible. It’s rare to find designs online which are based on a modular grid, but it is the perfect choice for this Pintori-inspired, large screen design. This modular grid is comprised of three columns and rows.

    I add grid properties to the body element, then specify my column widths to fill all available space. To make sure there’s always enough height to show the content of each panel, I use Grid’s minmax value, setting the minimum height at 300px and the maximum at 1fr:

    @media (min-width: 64em) {
    
    body {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: repeat(3, minmax(300px, 1fr));
    gap: 1rem;
    min-height: 100vh; }
    }
    

    Elements in this design don’t overlap, so I use grid-template-areas for their simplicity. This design has nine grid areas, and I give each one a single letter name, a–h. As the letter d is used for two adjacent areas, the item placed using that letter will occupy both:

    body {
    grid-template-areas: 
    "a b c"
    "d d e"
    "f g h"; }
    

    In this large screen implementation, the CSS Grid minmax value controls the height of my rows, making the min-height I applied earlier redundant:

    [id*="panel"] {
    min-height: none; }
    

    I place my panels using area names which allows me to change where they appear in my layout without altering their position in my HTML:

    #panel-a { grid-area: a; }
    #panel-b { grid-area: b; }
    #panel-c { grid-area: c; }
    #panel-d { grid-area: d; }
    #panel-e { grid-area: e; }
    #panel-f { grid-area: f; }
    #panel-g { grid-area: g; }
    p { grid-area: h; }
    

    While the design of my panels remains consistent across screen sizes, there’s one panel where the content and backgrounds change for larger screens. This panel contains the familiar Morris logo and what appears to be the main headline, “Style… in a BIG way.”

    To develop this panel, I first add a deep solid border at the top, followed by a data URI background image:

    #panel-d {
    border-top: 15px solid #262626;
    background-image: url("data:image/svg+xml…"); }
    

    Then, I add a second gradient background image which creates the black panel and two vertical yellow stripes:

    #panel-d {
    background-image: url("data:image/svg+xml…"),
    linear-gradient(
    to right, 
    #fff,
    #fff 280px,
    #B5964D 280px,
    #B5964D 320px,
    #fff 320px,
    #fff 335px,
    #262626 335px,
    #262626 calc(100% - 40px),
    #F2C867 calc(100% - 40px),
    #F2C867 100%); }
    

    Earlier in my process, I used a :before pseudo-element to add the Morris logo to this design. For large screens, I reposition that logo to the bottom-left of my panel:

    #panel-d
    position: relative; }
    #panel-d:before {
    position: absolute;
    bottom: 0;
    left: 0;
    margin: 0; }
    

    My large headline is immediately descended from the HTML body and is not part of this panel, making it tricky to position across flexible screen sizes. To reproduce my design precisely, without compromising accessibility, I first use an accessible method to hide this headline visually for people who use screen readers:

    h1 { 
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
    white-space: nowrap; }
    

    Then, I reinstate the headline’s text using generated content and an :after pseudo-element. I position it to the bottom-right of my panel and replicate its bold, condensed, italic style:

    #panel-d:after {
    content: "Style… in a BIG way";
    position: absolute;
    bottom: 0;
    right: 0;
    font-family: "moderna_sans-bold-cnd-it";
    font-size: 2.8rem;
    line-height: 1;
    text-align: right; }
    
    Pintori-inspired design
    Left: Monochrome version. Right: Full-color version of this Pintori-inspired design. (Large preview)
    Four complementary palettes include two colors on opposite sides of a color wheel
    Four complementary palettes include two colors on opposite sides of a color wheel. (Large preview)

    Complementary Palettes

    Colors that complement each other sit on opposite sides of a color wheel. But, although it’s easy to understand their mathematical relationship, working with complementary colors can be challenging.

    Adjacent complementary color combinations can look harsh, and rather than complement each other, can feel unharmonious. To prevent them from clashing, use shades, tints, or tones of complementary colors which will also help to expand your palette of usable colors.

    Alternatively, use split complementary colors where instead of opposing colors, the palette includes two colors on either side of the complementary.

    Split complementary palette includes two colors on either side of the complementary.
    Split complementary palette includes two colors on either side of the complementary. (Large preview)

    Complementing Colors

    Complementary pairings add personality to this Pintori-inspired design.
    Complementary pairings add personality to this Pintori-inspired design. (Large preview)

    Several colorfully complementary Morris Traveller blueprints overlap in my next Pintori-inspired design. The HTML needed to develop this design is as minimal as the depictions of this car. A banner division includes an SVG of the Morris logo, and the main element contains the headline and running text.

    But, the flexibility of this design across several screen sizes comes from using two picture elements, each containing three images. I include one picture element in the header, then another in my footer:

    <div class="banner">
      <svg>…</svg>
    </div>
    
    <header>
      <picture>
        <source media="(min-width: 72em)">
        <source media="(min-width: 48em)">
        <img>
      </picture>
    </header>
    
    <main>
      <h1><b>…</b></h1>
      <p>…</p>
    </main>
    
    <footer>
      <picture>
        <source media="(min-width: 72em)">
        <source media="(min-width: 48em)">
        <img>
      </picture>
    </footer>
    

    Every one of my development projects starts by adding the now familiar foundation styles, this time adding an off-white background color and almost black sans-serif text:

    body {
    background-color: #f3f2f2;
    font-family: "moderna_sans-light";
    color: #262626; }
    

    I align the content of my banner division to the centre, then set the logo’s maximum width to a diminutive 150px:

    .banner {
    text-align: center; }
    
    .banner svg {
    max-width: 150px; }
    

    The main headline in this design is set in the bold, condensed, italic style of Moderna Sans:

    h1 {
    font-family: "moderna_sans-bold-cnd-it";
    font-size: 2.027rem;
    font-weight: normal;
    line-height: 1.2; }
    

    Part of this headline is enclosed within a span element which enables me to change its color to match other aspects of this design, including the bull emblem at the center of the Morris Motors logo:

    h1 span {
    color: #df4561; }
    
    #logo .emblem {
    fill: #df4561; }
    

    On small screens, both the header and footer contain a single Traveller image. When there’s space to place two Travellers side-by-side, a browser changes the images in the two picture elements.

    For medium-size screens, I make use of the extra space available space and introduce a four-column symmetrical grid:

    @media (min-width: 48em) {
    
    body {
    display: grid;
    grid-template-columns: repeat(4, 1fr); }
    }
    

    I place the banner division in the two centre columns, centre my logo, then shift it vertically to fit between the bumpers of my two Travellers:

    .banner {
    grid-column: 2 / 4; 
    text-align: center;
    transform: translateY(2vh); }
    

    Both my header and footer span the grid from edge to edge, while placing the main content into the two centre columns creates a comfortable measure:

    header,
    footer {
    grid-column: 1 / -1; }
    main {
    grid-column: 2 / 4; }
    

    The most significant changes to the layout of this design can be seen at larger screen sizes. Despite their names, you needn’t place a header or footer element at the top and bottom of a layout. They can be placed anywhere within a design, including on the left or right.

    For more precise control over my layout, I increase the number of columns in my grid from four to eight, then introduce two rows. The first row has a fixed height of 160px, while the height of the second will be dictated by the content:

    @media (min-width: 72em) {
    
    body {
    grid-template-columns: repeat(8, 1fr);
    grid-template-rows: 160px auto;
    column-gap: 2vw; }
    }
    

    I reposition my banner division across three columns and set the main element below to match:

    .banner,
    main {
    grid-column: 3 / 5; }
    

    Then, I place the footer into the first three columns, and the header into the final four to create an asymmetrical layout from the symmetrical grid:

    header {
    grid-column: 5 / -1; }
    
    footer {
    grid-column: 1 / 4; }
    

    Both header and footer fill the height of my grid from top to bottom:

    header,
    footer {
    grid-row: 1 / 3; }
    

    While the banner division occupies the first row:

    .banner {
    grid-row: 1;  }
    

    And the main element fits neatly underneath it:

    main {
    grid-row: 2 / 3;
    z-index: 2; }
    

    Implementing light themes and dark designs has become part of everyday product and website design since Apple introduced a dark mode to iOS and macOS. Developing dark/light modes is easy, and there’s now a widely supported media query for this user preference. There are three values to choose from:

    • no-preference: Someone hasn’t expressed a preference
    • light: Someone has selected a light theme
    • dark: Someone has chosen a dark theme

    Introducing a dark mode version of this design involves little more than adding changes to certain color values within that media query. For example, by reversing the background and foreground text colors, and changing the path fill colors in my SVG logo:

    @media (prefers-color-scheme: dark) {
    
    body {
    background-color: #262626;
    color: #fff; }
    #logo .metal,
    #logo .emblem {
    fill: #fff; }
    }
    

    Deciding on dark mode colors sometimes involves more than simply inverting them, making white backgrounds black, and the black text white. Pure white text on full black backgrounds makes reading long passages of text tiring for the eye, so consider softening this contrast by using an off-white:

    body {
    color: #f3f2f2; }
    

    Sometimes, even vibrant complementary colors can appear different when they’re placed against a dark background. Thankfully, CSS filters can increase a color’s brightness, saturation, or both, with no need to export a different version of a file for darker backgrounds:

    header img,
    footer img {
    filter: saturate(1.5) brightness(1.1); }
    
    Vivid complementary colors against a dark background.
    Vivid complementary colors against a dark background. (Large preview)

    Brightening Colors

    Increased lightness and saturation
    Increasing lightness and saturation makes colors appear more vibrant against dark backgrounds. (Large preview)

    In my final Pintori-inspired design, colorful rectangles float above the dark grey background. This design needs just three structural elements; a header which again includes the Morris Motors logo, a figure element which contains not one, not two, but three outline images of the Morris Traveller, and the main element containing my running text:

    <header>
      <svg>…</svg>
    </header>
    
    <figure>
      <img>
      <img>
      <img>
    </figure>
    
    <main>
      <h1>…</h1>
      <p>…</p>
      <p>…</p>
    </main>
    

    To this minimal HTML, I add four purely presentational SVG images. As I don’t want these to be announced by assistive technologies, I add an ARIA hidden attribute to each of them:

    <svg id="bg-1" aria-hidden="true">…</svg>
    <svg id="bg-2" aria-hidden="true">…</svg>
    <svg id="bg-3" aria-hidden="true">…</svg>
    <svg id="bg-4" aria-hidden="true">…</svg>
    

    First, I specify foundation styles for background and foreground colors, then apply those same presentational SVG images to the background using data URIs:

    body {
    background-color: #262626; }
    background-image: 
    url("data:image/svg+xml…"),
    url("data:image/svg+xml…"),
    url("data:image/svg+xml…"),
    url("data:image/svg+xml…");
    color: #f3f2f2; }
    

    Then, I specify background repeat and position values, placing each SVG in the centre, and stacking them vertically on the page. Finally, I set their sizes:

    body {
    background-repeat: no-repeat;
    background-position:
    50% 20px,
    50% 240px,
    50% 460px,
    50% 680px;
    background-size: 
    200px 200px,
    300px 200px,
    200px 200px,
    100px 100px; }
    

    So the logo in my header matches the size of the SVG background behind it, I restrict its maximum width, then centre it using horizontal margins:

    header {
    max-width: 200px;
    margin: 0 auto; }
    

    Again, a horizontal scrolling panel is a useful way to present my three outlined Traveller images, so I set their figure’s display value to flex and prevent any horizontal overflow by setting its value to scroll:

    figure {
    display: flex;
    flex-wrap: nowrap;
    margin: 0;
    padding: 0;
    max-width: 100vw;
    overflow-x: scroll; }
    

    Then, I specify a flex-basis value and an height to match:

    figure img {
    flex-grow: 1;
    flex-basis: 320px;
    height: 320px; }
    

    I applied my four colorful SVGs as background images, so I don’t want them to appear on small screens. Using attribute selectors to precisely match a style’s property and value is an ideal way to target elements without resorting to additional class attributes:

    [aria-hidden="true"] {
    display: none; }
    

    This design needs only one media query breakpoint to apply layout styles for medium and large screens. I apply eight equal-width columns and eight rows, then remove the background images I applied for small screens:

    @media (min-width: 48em) {
    
    body {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    grid-template-rows: repeat(8, auto);
    background-image: none; }
    

    Then, I place the header and main elements between line numbers in my grid:

    header {
    grid-column: 1;
    grid-row: 1; }
    main {
    grid-column: 5 / 8;
    grid-row: 5 / 7; }
    

    I need to place the figure’s images and division onto my grid, not the figure itself, so I change its display property to contents, which effectively removes it from the DOM for styling purposes:

    figure {
    display: contents; }
    

    Then, I place each Traveller image into a different set of grid columns and rows, which alters their sizes along with their positions:

    figure img:nth-of-type(1) {
    grid-column: 3 / 6;
    grid-row: 2 / 4; }
    
    figure img:nth-of-type(2) {
    grid-column: 5 / 8;
    grid-row: 2 / 5; }
    
    figure img:nth-of-type(3) {
    grid-column: 3 / 4;
    grid-row: 5 / 6; }
    

    CSS transforms are ideal tools for fine-tuning the size and position of elements within grids’ constraints. They’re also useful for adding unusual touches to a design. I use rotate, scale, and translate to finely tune these images:

    figure img:nth-of-type(1) {
    transform: rotate(-20deg) translateX(-12rem); }
    figure img:nth-of-type(2) { transform: scale(1.1); }
    figure img:nth-of-type(3) { transform: scale(1.25); }
    

    I now reveal the colorful, presentational rectangles and push them behind my content by setting a low z-index value. Where these images overlap, a mix-blend-mode adds even more color to this design:

    [aria-hidden="true"] { 
    display: block;
    z-index: 0; 
    mix-blend-mode: multiply; }
    

    In this final step, I place these shapes onto my grid, using rotations to add even more personality to this already colorful design:

    .bg-1 {
    grid-column: 2 / 4;
    grid-row: 2 / 4;
    transform: rotate(-30deg);
    transform-origin: 75% 50%; }
    
    .bg-2 {
    grid-column: 4 / 8;
    grid-row: 2 / 5; }
    
    .bg-3 {
    grid-column: 3 / 5;
    grid-row: 4 / 6; }
    
    .bg-4 {
    grid-column: 4 / 5;
    grid-row: 6 / 7;
    transform: rotate(5deg);
    transform-origin: 0 0; }
    
    Left: The original colors for my design. Right: Increasing lightness and saturation by 10% increases their vibrancy.
    Left: The original colors for my design. Right: Increasing lightness and saturation by 10% increases their vibrancy. (Large preview)

    NB: Smashing members have access to a beautifully designed PDF of Andy’s Inspired Design Decisions magazine and full code examples from this article. You can also buy the PDF and examples from this, and every issue from Andy’s website.

    Read More From The Series

    Smashing Editorial
    (ra, yk, il)

    Source link