diff --git a/assets/css/_phx-liveview.scss b/assets/css/_phx-liveview.scss
index 4dadcd43..702bdd9f 100644
--- a/assets/css/_phx-liveview.scss
+++ b/assets/css/_phx-liveview.scss
@@ -9,7 +9,6 @@
 /* sets default bootstrap form-control styles if field not touched yet */
 .phx-no-feedback .form-control.is-valid,
 .phx-no-feedback .form-control.is-invalid {
-  padding: $input-padding-y $input-padding-x;
   border: $input-border-width solid $input-border-color;
   background-image: none;
 
diff --git a/assets/css/app.scss b/assets/css/app.scss
index d1d02301..384fc844 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -31,3 +31,36 @@
 .cursor-pointer {
   cursor: pointer !important;
 }
+
+/*
+  icons in input fields
+  */
+/* enable absolute positioning */
+.inner-addon {
+  position: relative;
+}
+
+/* style icon */
+.inner-addon > .icon {
+  position: absolute;
+  padding: 0.5625rem 0.5rem;
+  pointer-events: none;
+}
+
+/* align icon */
+.inner-addon > .icon.is-left {
+  left: 0px;
+}
+.inner-addon > .icon.is-right {
+  right: 0px;
+}
+
+/* add padding  */
+.left-addon input,
+.left-addon select {
+  padding-left: 2rem;
+}
+.right-addon input,
+.right-addon select {
+  padding-right: 2rem;
+}
diff --git a/assets/js/app.js b/assets/js/app.js
index aeee1e5f..5d161237 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -19,7 +19,9 @@ import "../node_modules/bootstrap-icons/icons/key-fill.svg"; // pw confirm field
 import "../node_modules/bootstrap-icons/icons/lock.svg"; // current pw field
 import "../node_modules/bootstrap-icons/icons/shield.svg"; // role
 // live tables
-import "../node_modules/bootstrap-icons/icons/backspace.svg"; // clear filter
+import "../node_modules/bootstrap-icons/icons/arrow-down-up.svg"; // sort
+import "../node_modules/bootstrap-icons/icons/funnel.svg"; // filter
+import "../node_modules/bootstrap-icons/icons/x-circle-fill.svg"; // clear filter
 import "../node_modules/bootstrap-icons/icons/sort-down-alt.svg";
 import "../node_modules/bootstrap-icons/icons/sort-up-alt.svg";
 import "../node_modules/bootstrap-icons/icons/chevron-left.svg";
diff --git a/lib/shift73k_web/live/user/registration.html.leex b/lib/shift73k_web/live/user/registration.html.leex
index 511a9666..0bc2afa8 100644
--- a/lib/shift73k_web/live/user/registration.html.leex
+++ b/lib/shift73k_web/live/user/registration.html.leex
@@ -9,8 +9,9 @@
 
   <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, novalidate: true, id: "reg_form"], fn f -> %>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+      <%= icon_div @socket, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
           value: input_value(f, :email),
           class: input_class(f, :email, "form-control"),
@@ -20,31 +21,22 @@
           phx_debounce: "blur",
           aria_describedby: error_ids(f, :email)
         %>
-      <%= label f, :email do %>
-        <%= icon_div @socket, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <%= error_tag f, :email %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+    <%= label f, :password, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+      <%= icon_div @socket, "bi-key", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password,
           value: input_value(f, :password),
           class: input_class(f, :password, "form-control"),
-          placeholder: "Password",
           maxlength: User.max_password,
           phx_debounce: "250",
           aria_describedby: error_ids(f, :password)
         %>
-      <%= label f, :password do %>
-        <%= icon_div @socket, "bi-key", [class: "icon baseline text-muted"] %>
-        Password
-      <% end %>
       <%= error_tag f, :password %>
     </div>
 
-
     <div class="mb-3">
       <%= submit (@trigger_submit && "Saving..." || "Register"),
           class: "btn btn-primary",
diff --git a/lib/shift73k_web/live/user/reset_password.html.leex b/lib/shift73k_web/live/user/reset_password.html.leex
index b29ae36c..049d15a5 100644
--- a/lib/shift73k_web/live/user/reset_password.html.leex
+++ b/lib/shift73k_web/live/user/reset_password.html.leex
@@ -9,39 +9,31 @@
 
   <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, novalidate: true, id: "pw_reset_form"], fn f -> %>
 
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+    <%= label f, :password, "New password", class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+      <%= icon_div @socket, "bi-key", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password,
           value: input_value(f, :password),
           class: input_class(f, :password, "form-control"),
-          placeholder: "New Password",
           maxlength: User.max_password,
           autofocus: true,
           aria_describedby: error_ids(f, :password)
         %>
-      <%= label f, :password do %>
-        <%= icon_div @socket, "bi-key", [class: "icon baseline text-muted"] %>
-        New password
-      <% end %>
       <%= error_tag f, :password %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
+    <%= label f, :password_confirmation, "Confirm new password", class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
+      <%= icon_div @socket, "bi-key-fill", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password_confirmation,
           value: input_value(f, :password_confirmation),
           class: input_class(f, :password_confirmation, "form-control"),
-          placeholder: "Confirm new password",
           maxlength: User.max_password,
           aria_describedby: error_ids(f, :password_confirmation)
         %>
-      <%= label f, :password do %>
-        <%= icon_div @socket, "bi-key-fill", [class: "icon baseline text-muted"] %>
-        Confirm new password
-      <% end %>
       <%= error_tag f, :password_confirmation %>
     </div>
 
-
     <div class="mb-3">
       <%= submit "Reset password",
           class: "btn btn-primary",
diff --git a/lib/shift73k_web/live/user/settings/email.html.leex b/lib/shift73k_web/live/user/settings/email.html.leex
index 2d1b6e47..d50ce902 100644
--- a/lib/shift73k_web/live/user/settings/email.html.leex
+++ b/lib/shift73k_web/live/user/settings/email.html.leex
@@ -4,8 +4,9 @@
 
   <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+      <%= icon_div @socket, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
           value: input_value(f, :email),
           class: input_class(f, :email, "form-control"),
@@ -14,29 +15,20 @@
           phx_debounce: "500",
           aria_describedby: error_ids(f, :email)
         %>
-      <%= label f, :email do %>
-        <%= icon_div @socket, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <%= error_tag f, :email %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
+    <%= label f, :current_password, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
+      <%= icon_div @socket, "bi-lock", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :current_password,
           value: input_value(f, :current_password),
           class: "form-control",
-          placeholder: "Current Password",
           aria_describedby: error_ids(f, :current_password)
         %>
-      <%= label f, :current_password do %>
-        <%= icon_div @socket, "bi-lock", [class: "icon baseline text-muted"] %>
-        Current password
-      <% end %>
       <%= error_tag f, :current_password %>
     </div>
 
-
     <div class="mb-3">
       <%= submit "Change email",
           class: "btn btn-primary",
diff --git a/lib/shift73k_web/live/user/settings/password.html.leex b/lib/shift73k_web/live/user/settings/password.html.leex
index 6d781dfd..a15d30ff 100644
--- a/lib/shift73k_web/live/user/settings/password.html.leex
+++ b/lib/shift73k_web/live/user/settings/password.html.leex
@@ -4,51 +4,39 @@
 
   <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+    <%= label f, :password, "New password", class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+      <%= icon_div @socket, "bi-key", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password,
           value: input_value(f, :password),
           class: input_class(f, :password, "form-control"),
-          placeholder: "New Password",
           maxlength: User.max_password,
           phx_debounce: "500",
           aria_describedby: error_ids(f, :password)
         %>
-      <%= label f, :password do %>
-        <%= icon_div @socket, "bi-key", [class: "icon baseline text-muted"] %>
-        New password
-      <% end %>
       <%= error_tag f, :password %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
+    <%= label f, :password_confirmation, "Confirm new password", class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
+      <%= icon_div @socket, "bi-key-fill", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password_confirmation,
           value: input_value(f, :password_confirmation),
           class: input_class(f, :password_confirmation, "form-control"),
-          placeholder: "Confirm new password",
           maxlength: User.max_password,
           aria_describedby: error_ids(f, :password_confirmation)
         %>
-      <%= label f, :password_confirmation do %>
-        <%= icon_div @socket, "bi-key-fill", [class: "icon baseline text-muted"] %>
-        Confirm new password
-      <% end %>
       <%= error_tag f, :password_confirmation %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
+    <%= label f, :current_password, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
+      <%= icon_div @socket, "bi-lock", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :current_password,
           value: input_value(f, :current_password),
           class: "form-control",
-          placeholder: "Current Password",
           aria_describedby: error_ids(f, :current_password)
         %>
-      <%= label f, :current_password do %>
-        <%= icon_div @socket, "bi-lock", [class: "icon baseline text-muted"] %>
-        Current password
-      <% end %>
       <%= error_tag f, :current_password %>
     </div>
 
@@ -68,10 +56,4 @@
     <%= hidden_input f, :params_token, value: Phoenix.Token.encrypt(Shift73kWeb.Endpoint, "login_params", @login_params) %>
   <% end %>
 
-  <%# hidden form to submit user for relogin after password change %>
-  <%#= form_for :user_login, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit], fn f -> %>
-    <%#= hidden_input f, :login_params_token, value: Phoenix.Token.encrypt(Shift73kWeb.Endpoint, "login_params", @login_params) %>
-    <%#= hidden_input f, :remember_me, value: false %>
-  <%# end %>
-
 </div>
diff --git a/lib/shift73k_web/live/user_management/form_component.html.leex b/lib/shift73k_web/live/user_management/form_component.html.leex
index 65607c0c..db0accd5 100644
--- a/lib/shift73k_web/live/user_management/form_component.html.leex
+++ b/lib/shift73k_web/live/user_management/form_component.html.leex
@@ -6,7 +6,9 @@
 
   <div class="modal-body">
 
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
+      <%= icon_div @socket, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
           value: input_value(f, :email),
           class: input_class(f, :email, "form-control"),
@@ -16,39 +18,26 @@
           phx_debounce: "250",
           aria_describedby: error_ids(f, :email)
         %>
-      <%= label f, :email do %>
-        <%= icon_div @socket, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <%= error_tag f, :email %>
     </div>
 
-
-    <div class="form-floating mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+    <%= label f, :password, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
+      <%= icon_div @socket, "bi-key", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password,
           value: input_value(f, :password),
           class: input_class(f, :password, "form-control"),
-          placeholder: "Password",
           maxlength: User.max_password,
           aria_describedby: error_ids(f, :password)
         %>
-      <%= label f, :password do %>
-        <%= icon_div @socket, "bi-key", [class: "icon baseline text-muted"] %>
-        Password
-      <% end %>
       <%= error_tag f, :password %>
     </div>
 
-
-
-
     <%= if Roles.can?(@current_user, %User{}, :edit_role) do %>
-      <div class="form-floating">
+      <%= label f, :role, class: "form-label" %>
+      <div class="inner-addon left-addon mb-3">
+        <%= icon_div @socket, "bi-shield", [class: "icon is-left text-muted fs-5"] %>
         <%= select f, :role, Enum.map(User.roles(), fn {k, _v} -> {String.capitalize(Atom.to_string(k)), k} end), class: "form-select" %>
-        <%= label f, :role do %>
-          <%= icon_div @socket, "bi-shield", [class: "icon baseline text-muted"] %>
-          User role
-        <% end %>
         <span class="valid-feedback text-primary" style="display: block;">
           <%= role_description(input_value(f, :role)) %>
         </span>
@@ -57,7 +46,6 @@
       <%= hidden_input f, :role, value: input_value(f, :role) %>
     <% end %>
 
-
   </div>
   <div class="modal-footer">
 
diff --git a/lib/shift73k_web/live/user_management/index.html.leex b/lib/shift73k_web/live/user_management/index.html.leex
index 8cdb428e..8861fc1a 100644
--- a/lib/shift73k_web/live/user_management/index.html.leex
+++ b/lib/shift73k_web/live/user_management/index.html.leex
@@ -22,34 +22,100 @@
 </h2>
 
 <%# filtering and new item creation %>
-<div class="d-flex flex-column flex-sm-row justify-content-between d-flex align-items-start mb-3">
+<div class="row justify-content-between align-items-start align-items-sm-end mb-3">
 
+  <div class="col-12 col-sm-6 col-lg-4 col-xl-3">
   <%= live_patch to: Routes.user_management_index_path(@socket, :new, Enum.into(@query, [])),
       class: "btn btn-primary mb-3 mb-sm-0" do %>
     <%= icon_div @socket, "bi-person-plus", [class: "icon baseline me-1"] %>
     New User
   <% end %>
-
-  <%= form_for :filter, "#", [phx_change: "filter-change"], fn flt -> %>
-  <div class="input-group">
-
-    <%= text_input flt, :filter, name: "filter", class: "form-control", placeholder: "Filter users...", value: @query.filter %>
-
-    <%= if @query.filter == "" do %>
-    <button class="btn btn-outline-primary" type="button" aria-label="Clear filter" aria-disabled="true" disabled>
-    <% else %>
-    <button class="btn btn-primary" type="button" aria-label="Clear filter" phx-click="filter-clear">
-    <% end %>
-      <%= icon_div @socket, "bi-backspace", [class: "icon baseline"], [role: "img", aria_hidden: false] %>
-    </button>
-
   </div>
+
+  <div class="col-12 d-block d-sm-none">
+  <%= form_for :sort, "#", [phx_change: "sort-by-change"], fn srt -> %>
+    <%= label srt, :sort_by, class: "visually-hidden" %>
+    <div class="input-group inner-addon left-addon mb-3 mb-sm-0">
+      <%= icon_div @socket, "bi-arrow-down-up", [class: "icon is-left text-muted fs-5", style: "z-index:1001;"], [style: "padding: 1px;"] %>
+      <%= Phoenix.HTML.Form.select srt, :sort_by, ["Email": "email", "Role": "role", "Created at": "inserted_at"], value: @query.sort_by, class: "form-select" %>
+      <button class="btn btn-primary" type="button" aria-label="Change sort order" phx-click="sort-order-change">
+      <%= icon_div @socket, (@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"), [class: "icon baseline"] %>
+      </button>
+    </div>
+  <% end %>
+  </div>
+
+  <div class="col-12 col-sm-6 col-lg-4 col-xl-3">
+  <%= form_for :filter, "#", [phx_change: "filter-change"], fn flt -> %>
+    <%= label flt, :filter, class: "visually-hidden" %>
+    <div class="inner-addon left-addon right-addon">
+      <%= icon_div @socket, "bi-funnel", [class: "icon is-left text-muted fs-5"] %>
+      <%= if @query.filter != "" do %>
+        <%= icon_div @socket, "bi-x-circle-fill", [class: "icon is-right text-primary fs-5"], [role: "img", aria_hidden: false, aria_label: "Clear filter", class: "cursor-pointer pe-auto", phx_click: "filter-clear"] %>
+      <% end %>
+      <%= text_input flt, :filter,
+          name: "filter",
+          value: @query.filter,
+          class: "form-control",
+          placeholder: "Filter users",
+          aria_label: "Filter users"
+        %>
+    </div>
+  <% end %>
+  </div>
+</div>
+
+<%# mobile data cards %>
+<div class="d-block d-sm-none">
+
+
+  <%= if !@page do %>
+    <div class="card">
+      <div class="spinner-border text-primary my-5 mx-auto" role="status">
+        <span class="visually-hidden">Loading...</span>
+      </div>
+    </div>
+  <% else %>
+    <%= for user <- @page.entries do %>
+      <div class="card mb-3">
+        <div class="card-body">
+          <dl class="row">
+            <dt class="col-sm-3">Email</dt>
+            <dd class="col-sm-9"><%= user.email %></dd>
+            <dt class="col-sm-3">Role</dt>
+            <dd class="col-sm-9"><%= user.role |> Atom.to_string() |> String.capitalize() %></dd>
+            <dt class="col-sm-3">Created at</dt>
+            <dd class="col-sm-9"><%= dt_out(user.inserted_at) %></dd>
+            <dt class="col-sm-3">
+              Confirmed?
+              <span class="visually-hidden"><%= user.confirmed_at && "Yes" || "No" %></span>
+              <input type="checkbox" class="form-check-input ms-1" aria-hidden="true" <%= user.confirmed_at && "checked" || "" %>>
+            </dt>
+          </dl>
+
+          <%= if Roles.can?(@current_user, user, :edit) do %>
+            <%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm text-nowrap" do %>
+              <%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+              Edit
+            <% end %>
+          <% end %>
+
+          <%= if Roles.can?(@current_user, user, :delete) do %>
+            <button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>">
+              <%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+              Delete
+            </button>
+          <% end %>
+
+        </div>
+      </div>
+    <% end %>
   <% end %>
 
 </div>
 
-<%# main data table %>
-<div class="table-responsive">
+<%# non-mobile main data table %>
+<div class="table-responsive d-none d-sm-block">
   <table class="table">
 
     <thead>
@@ -102,13 +168,8 @@
             <td class="align-middle"><%= user.role |> Atom.to_string() |> String.capitalize() %></td>
             <td class="align-middle" style="white-space: nowrap;"><%= dt_out(user.inserted_at) %></td>
             <td class="align-middle">
-              <%= if user.confirmed_at do %>
-                <span class="visually-hidden">Yes</span>
-                <%= icon_div @socket, "bi-check", [class: "icon baseline fs-4 text-success"], [role: "img", aria_hidden: false] %>
-              <% else %>
-                <span class="visually-hidden">No</span>
-                <%= icon_div @socket, "bi-x", [class: "icon baseline fs-4 text-warning"], [role: "img", aria_hidden: false] %>
-              <% end %>
+              <span class="visually-hidden"><%= user.confirmed_at && "Confirmed" || "Not confirmed" %></span>
+              <input type="checkbox" class="form-check-input" aria-hidden="true" <%= user.confirmed_at && "checked" || "" %>>
             </td>
             <td class="align-middle text-end text-nowrap">
 
@@ -153,7 +214,7 @@
           class: "form-select"
         %>
     <% end %>
-    <span class="ms-2"><%= @page.total_entries %> found</span>
+    <span class="ms-2"><%= @page.total_entries %> total</span>
   </div>
 
   <%# main pagination %>
diff --git a/lib/shift73k_web/templates/user_confirmation/new.html.eex b/lib/shift73k_web/templates/user_confirmation/new.html.eex
index 5053dad7..1fb18e7e 100644
--- a/lib/shift73k_web/templates/user_confirmation/new.html.eex
+++ b/lib/shift73k_web/templates/user_confirmation/new.html.eex
@@ -9,7 +9,9 @@
 
   <%= form_for :user, Routes.user_confirmation_path(@conn, :create), [class: "needs-validation", novalidate: true], fn f -> %>
 
-    <div class="form-floating mb-3">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3">
+      <%= icon_div @conn, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
           value: @current_user && @current_user.email || "",
           placeholder: "e.g., babka@73k.us",
@@ -18,10 +20,6 @@
           required: true,
           autofocus: !@current_user
         %>
-      <%= label f, :email do %>
-        <%= icon_div @conn, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <span class="invalid-feedback">must be a valid email address</span>
     </div>
 
diff --git a/lib/shift73k_web/templates/user_reset_password/new.html.eex b/lib/shift73k_web/templates/user_reset_password/new.html.eex
index aa2f5476..06aefbc1 100644
--- a/lib/shift73k_web/templates/user_reset_password/new.html.eex
+++ b/lib/shift73k_web/templates/user_reset_password/new.html.eex
@@ -9,17 +9,16 @@
 
   <%= form_for :user, Routes.user_reset_password_path(@conn, :create), [class: "needs-validation", novalidate: true], fn f -> %>
 
-    <div class="form-floating mb-3">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3">
+      <%= icon_div @conn, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
-          class: "form-control",
           placeholder: "e.g., babka@73k.us",
+          class: "form-control",
           maxlength: User.max_email,
-          required: true
+          required: true,
+          autofocus: true
         %>
-      <%= label f, :email do %>
-        <%= icon_div @conn, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <span class="invalid-feedback">must be a valid email address</span>
     </div>
 
diff --git a/lib/shift73k_web/templates/user_session/new.html.eex b/lib/shift73k_web/templates/user_session/new.html.eex
index 7b62f589..310f089d 100644
--- a/lib/shift73k_web/templates/user_session/new.html.eex
+++ b/lib/shift73k_web/templates/user_session/new.html.eex
@@ -15,36 +15,28 @@
       </div>
     <% end %>
 
-
-    <div class="form-floating mb-3">
+    <%= label f, :email, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3">
+      <%= icon_div @conn, "bi-at", [class: "icon is-left text-muted fs-5"] %>
       <%= email_input f, :email,
           class: "form-control",
           placeholder: "e.g., babka@73k.us",
           maxlength: User.max_email,
           required: true
         %>
-      <%= label f, :email do %>
-        <%= icon_div @conn, "bi-at", [class: "icon baseline text-muted"] %>
-        Email
-      <% end %>
       <span class="invalid-feedback">must be a valid email address</span>
     </div>
 
-
-    <div class="form-floating mb-3">
+    <%= label f, :password, class: "form-label" %>
+    <div class="inner-addon left-addon mb-3">
+      <%= icon_div @conn, "bi-lock", [class: "icon is-left text-muted fs-5"] %>
       <%= password_input f, :password,
           class: "form-control",
-          placeholder: "Password",
           required: true
         %>
-      <%= label f, :password do %>
-        <%= icon_div @conn, "bi-lock", [class: "icon baseline text-muted"] %>
-        Password
-      <% end %>
       <span class="invalid-feedback">password is required</span>
     </div>
 
-
     <div class="form-check mb-3 no-valid-style">
       <%= checkbox f, :remember_me, class: "form-check-input" %>
       <%= label f, :remember_me, "Keep me logged in for 60 days", class: "form-check-label" %>
diff --git a/lib/shift73k_web/views/error_helpers.ex b/lib/shift73k_web/views/error_helpers.ex
index 0c3703c1..76b8145b 100644
--- a/lib/shift73k_web/views/error_helpers.ex
+++ b/lib/shift73k_web/views/error_helpers.ex
@@ -22,7 +22,6 @@ defmodule Shift73kWeb.ErrorHelpers do
     content_tag(:span, translate_error(err), opts)
   end
 
-
   defp error_common_opts(form, field, append, opts) do
     Keyword.put(opts, :phx_feedback_for, input_id(form, field))
     |> Keyword.update(:class, append, fn c -> "#{append} #{c}" end)
@@ -37,6 +36,7 @@ defmodule Shift73kWeb.ErrorHelpers do
 
   def error_ids(%Phoenix.HTML.Form{} = form, field) do
     input_id = input_id(form, field)
+
     form.errors
     |> Keyword.get_values(field)
     |> Stream.with_index()
@@ -45,15 +45,13 @@ defmodule Shift73kWeb.ErrorHelpers do
   end
 
   def input_class(form, field, classes \\ "") do
-    case form.source.action do
-      nil ->
-        classes
-
-      _ ->
-        case Keyword.has_key?(form.errors, field) do
-          true -> "#{classes} is-invalid"
-          _ -> "#{classes} is-valid"
-        end
+    if form.source.action do
+      case Keyword.has_key?(form.errors, field) do
+        true -> "#{classes} is-invalid"
+        _ -> "#{classes} is-valid"
+      end
+    else
+      classes
     end
   end
 
diff --git a/lib/shift73k_web/views/icon_helpers.ex b/lib/shift73k_web/views/icon_helpers.ex
index 32f15b37..048373f8 100644
--- a/lib/shift73k_web/views/icon_helpers.ex
+++ b/lib/shift73k_web/views/icon_helpers.ex
@@ -16,7 +16,10 @@ defmodule Shift73kWeb.IconHelpers do
     opts = aria_hidden?(opts)
 
     content_tag(:svg, tag_opts(name, opts)) do
-      tag(:use, "xlink:href": Routes.static_path(conn, "/images/icons.svg##{name}"))
+      ~E"""
+      <%= if title = Keyword.get(opts, :aria_label), do: content_tag(:title, title) %>
+      <%= tag(:use, "xlink:href": Routes.static_path(conn, "/images/icons.svg##{name}")) %>
+      """
     end
   end