How I managed to customize AWS Amplify login screens
Custom Login/Sign Up styling for Amplify Vue & Cognito
Origionally posted on Medium Feb 2019: Bryce Howitson
I’m an interface designer who does a lot of CSS and some Javascript for the apps I design. I’ve been struggling (read, bashing my head against a wall) to customize the visual style/layout of AWS Amplify Vue’s Cognito authentication flow for several months across multiple projects.
Now, I completely understand why the development community loves Amplify as it makes interacting with AWS SDKs much easier. Amplify also produces something accessible with an interface that works pretty well out of the box. Unfortunately, as a designer, I find the default styling horrific not to mention that it doesn’t even remotely match the brands for which I’m creating apps.
So I rolled up my sleeves and started punching. I’m happy to report that I’ve finally beat Amplify Vue defaults into submission, well mostly.
Default on the left and my customized version on the right. Apparently, Amplify is still fighting with my existing App CSS even before I take on customizations.
Where I landed:
This isn’t a perfect solution, but I estimate about 80% success in my ability to change Amplify’s pre-baked interface to match my designs.
What can be changed: Colors, layout, and any content that wraps around the login/signup fields. Basically the stuff you can do with CSS and containers.
What can’t be changed: Form field order, label text, placeholder text, validation or error messages as they’re part of the rendered HTML.
Note: Some of this solution applies to AWS-Amplify-Vue specifically but the styling portions should work for React, Angular, etc.
Problems I encountered
The major problem when it comes to modifying layout is CSS selectors. The Amplify Vue package contains modularized CSS from Webpack. This means classes/ids look something like this: class="form __input__ xFAr73"
Each time the library is recompiled with Webpack the string at the end of the class changes. So it's a bad idea to write CSS to override these classes because they could change unexpectedly. CSS Modules force class names to be highly specific (the intent is to keep include CSS from fighting with your app CSS) but it also makes it more difficult to override the styling that comes pre-made with Amplify.
Second, I wanted to add branding content around the individual forms. Obviously, we’re doing this without modifying the Amplify templates to ensure their functionality remains, so it was difficult to achieve much branding when the Amplify views represented a full route (think individual screen not part of another screen).
Finally, I would like to modify what form fields are called and change where/how validation messages appear. As far as I can tell, you are able to modify some of the sign-up form rendering via configuration but the other screens are off limits. Unfortunately, I wasn’t able to achieve this last bit entirely, but the pre-built Amplify forms are actually pretty good.
Steps to customize Amplify Vue’s Cognito authentication flow
1. Create a new component
Create a new view/component to contain Amplify components and bus (render decisions like Vue Router). This allows custom content like branding to appear in the context of the login or sign up.
- To make sure I had everything, I basically copied all the component code from
/node-modules/aws-amplify-vue/src/components/authenticator/Authenticator.vue
into my new component - You’ll need to change the imports to reference the package
import { components, AmplifyEventBus, AmplifyPlugin } from ‘aws-amplify-vue’;
- Now you can add additional content inside the template to show for EVERY login screen or use v-if to show content if specific components are rendered. For example, if you want something to only show on the sign in screen you could do this:
<h2 v-if="displayMap.showSignIn">Please Login</h2>
- Don’t forget to update the Router to your new component.
2. Improve styling by adding IDs & Classes to your component
Wrap Amplify components with an ID to increase CSS Specificity. In practical terms, it means its easier to override existing styling without relying on !important
to win.
- I gave the first
div
insidetemplate
an id of “auth” - Since we might want to change styles based on the screen I also added logic to change the class based on which content is displaying. Notice how this relies on Amplify’s map the same way showing components does.
<div id="auth" v-bind:class="[
displayMap.showSignIn ? 'signIn' : '',
displayMap.showSignUp ? 'signUp' : '',
displayMap.showConfirmSignUp ? 'confirmAccount' : '',
displayMap.showConfirmSignIn ? 'confirmSignIn' : '',
displayMap.showForgotPassword ? 'forgot' : '',
displayMap.requireNewPassword ? 'newPass' : '',
displayMap.showMfa ? 'showMfa' : ''
]">
3. Override styling with defaults
Reset Amplify styling back to browser defaults. You can skip this step but it means finding every attribute that differs from your design and needs an override. This is really pretty easy with some newish CSS but be warned it doesn’t work across all browsers ever.
At first, I tried css all:initial
on everything but it turned out to be a pretty nuclear option. Things like div’s didn’t retain block level status and even my CSS Reset was overridden. So… a final solution was to manually reset most attributes of div and span and then a much harder reset on input, button and anchor tags. I also decided to hide any Labels as I couldn’t change the text on most fields.
div,
span {
margin: 0;
padding: 0;
background: none;
text-align: left;
max-width: 100%;
min-width: 0;
border-radius: 0;
box-shadow: none;
}
input,
button,
a {
all: initial;
width: auto;
margin: 0;
padding: 0;
cursor: pointer;
}
4. Decide what to change
Once you know what elements are called, use pattern matching in CSS selectors to target prefixes in class names, thus avoiding changes when Amplify updates and run’s Webpack. I opened up the web inspector to see what I needed to style.
Notice, how messy all that stuff is. I was able to style most of my app with only a few of the selectors and :nth-child()
Your mileage may vary. Most of what I wanted to change were the Form __formField___ 2DWht
divs. I struggled with the best way to reference those elements without worrying if they changed. I finally remembered that you can pattern match selectors in CSS so I referenced the above like this: [class^=”Form__formField”] {attribute: value}
This is called “Substring Matching” and proved to be super useful in this case. Here’s a good explanation of how they work.
Conclusion
Once I had a wrapper with an easily accessible ID, classes denoting the current content and access to the CSS Modularized elements, styling was pretty straight forward. Hopefully, this writeup saves someone else all the headache I experienced.
By day, I’m a product strategist consulting on all things digital and a Design Sprint Master. By night I’m a Google Expert, a startup mentor and writing a book about getting started in UX. Follow me on Twitter @howitson.