diff --git a/.iex.exs b/.iex.exs
index 0cc62318..66306574 100644
--- a/.iex.exs
+++ b/.iex.exs
@@ -3,5 +3,7 @@ import Ecto.Query
 alias Shift73k.Repo
 alias Shift73k.Accounts
 alias Shift73k.Accounts.User
+alias Shift73k.Shifts
+alias Shift73k.Shifts.Shift
 alias Shift73k.Shifts.Templates
 alias Shift73k.Shifts.Templates.ShiftTemplate
diff --git a/assets/css/_bs-colors.scss b/assets/css/_bs-colors.scss
index a6648039..352e5727 100644
--- a/assets/css/_bs-colors.scss
+++ b/assets/css/_bs-colors.scss
@@ -3,7 +3,7 @@ $primary: #662c91;
 $secondary: #ee6c4d;
 $success: #3f784c;
 $info: #3f84e5;
-$warning: #fcca46;
+$warning: #ffec51;
 $light: $gray-200;
 $dark: $gray-800;
 
diff --git a/assets/css/_bs-load.scss b/assets/css/_bs-load.scss
index f1f0e784..b7c2aa2f 100644
--- a/assets/css/_bs-load.scss
+++ b/assets/css/_bs-load.scss
@@ -28,7 +28,7 @@
 @import "../node_modules/bootstrap/scss/nav";
 @import "../node_modules/bootstrap/scss/navbar";
 @import "../node_modules/bootstrap/scss/card";
-// @import "../node_modules/bootstrap/scss/accordion";
+@import "../node_modules/bootstrap/scss/accordion";
 // @import "../node_modules/bootstrap/scss/breadcrumb";
 @import "../node_modules/bootstrap/scss/pagination";
 // @import "../node_modules/bootstrap/scss/badge";
diff --git a/assets/css/app.scss b/assets/css/app.scss
index a20a027c..722c55f2 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -63,12 +63,32 @@
 }
 
 /* calendar table rounded */
-// table.table.table-rounded,
-// table.table.table-rounded thead,
-// table.table.table-rounded thead tr,
-// table.table.table-rounded thead tr th:first-child {
-//   border-radius: 10px 0 0 10px;
-// }
+table.table.table-calendar thead tr th,
+table.table.table-calendar tbody tr td {
+  width: 14%;
+}
+table.table.table-calendar tbody tr td {
+  font-size: $font-size-sm;
+  height: 3.5rem;
+  padding: 0.2rem 0.4rem;
+  @include media-breakpoint-up(md) {
+    height: 4.5rem;
+    padding: 0.2rem 0.5rem;
+  }
+  @include media-breakpoint-up(lg) {
+    font-size: $font-size-base;
+    height: 5.5rem;
+    padding: 0.3rem 0.5rem;
+  }
+  @include media-breakpoint-up(xl) {
+    height: 6.5rem;
+    padding: 0.35rem 0.5rem;
+  }
+  @include media-breakpoint-up(xxl) {
+    height: 7.5rem;
+    padding: 0.4rem 0.75rem;
+  }
+}
 
 table.table.table-rounded {
   border-collapse: separate;
@@ -117,3 +137,41 @@ table.table.table-rounded {
     }
   }
 }
+
+/* selected days background triangle color */
+.bg-triangle-primary {
+  background: linear-gradient(
+    to bottom right,
+    $primary 0%,
+    $primary 50%,
+    $secondary 50%,
+    $secondary 100%
+  );
+}
+.bg-triangle-info {
+  background: linear-gradient(
+    to bottom right,
+    $info 0%,
+    $info 50%,
+    $secondary 50%,
+    $secondary 100%
+  );
+}
+.bg-triangle-light {
+  background: linear-gradient(
+    to bottom right,
+    $light 0%,
+    $light 50%,
+    $secondary 50%,
+    $secondary 100%
+  );
+}
+.bg-triangle-white {
+  background: linear-gradient(
+    to bottom right,
+    $white 0%,
+    $white 50%,
+    $secondary 50%,
+    $secondary 100%
+  );
+}
diff --git a/assets/js/_bs_collapse.js b/assets/js/_bs_collapse.js
new file mode 100644
index 00000000..c3c04074
--- /dev/null
+++ b/assets/js/_bs_collapse.js
@@ -0,0 +1,61 @@
+// Hook for custom liveview bootstrap collapse handling
+import Collapse from "bootstrap/js/dist/collapse";
+
+export const BsCollapse = {
+  mounted() {
+    // when the liveview mounts, create the BS collapse object
+    const collapse = new Collapse(this.el, { toggle: false });
+
+    this.handleEvent("toggle-template-details", ({ targetId }) => {
+      if (this.el.id == targetId) {
+        collapse.toggle();
+      }
+    });
+
+    // this.el.addEventListener("show.bs.collapse", (event) => {
+    //   this.pushEvent("collapse-show", { target_id: event.target.id });
+    // });
+
+    this.el.addEventListener("shown.bs.collapse", (event) => {
+      this.pushEvent("collapse-shown", { target_id: event.target.id });
+    });
+
+    // this.el.addEventListener("hide.bs.collapse", (event) => {
+    //   this.pushEvent("collapse-hide", { target_id: event.target.id });
+    // });
+
+    this.el.addEventListener("hidden.bs.collapse", (event) => {
+      this.pushEvent("collapse-hidden", { target_id: event.target.id });
+    });
+  },
+};
+
+// export const BsModal = {
+//   mounted() {
+//     // when the liveview mounts, create the BS modal
+//     const modal = new Modal(this.el);
+//     // and trigger BS modal to show
+//     modal.show();
+
+//     // when the BS modal hides, send 'close' to the liveview
+//     this.el.addEventListener("hidden.bs.modal", (event) => {
+//       this.pushEventTo(`#${this.el.getAttribute("id")}`, "close", {});
+//       modal.dispose();
+//     });
+
+//     // liveview can send this event to tell BS modal to close
+//     // ex.: on successful form save, instead of immediate redirect
+//     //   this event hides the BS modal, which triggers the above,
+//     //   which sends 'close' to liveview and disposes the BS modal
+//     this.handleEvent("modal-please-hide", (payload) => {
+//       modal.hide();
+//     });
+//   },
+
+//   destroyed() {
+//     // when the liveview is destroyed,
+//     // modal-backdrop must be forcibly removed
+//     const backdrop = document.querySelector(".modal-backdrop");
+//     if (backdrop) backdrop.parentElement.removeChild(backdrop);
+//   },
+// };
diff --git a/assets/js/_bs_modal.js b/assets/js/_bs_modal.js
index 981b3bc9..ce2adb67 100644
--- a/assets/js/_bs_modal.js
+++ b/assets/js/_bs_modal.js
@@ -1,7 +1,6 @@
 // Helping bootstrap modals work with liveview
 // preserving animations
 import Modal from "bootstrap/js/dist/modal";
-// import { Modal } from "bootstrap";
 
 export const BsModal = {
   mounted() {
diff --git a/assets/js/app.js b/assets/js/app.js
index 7c7ad521..62e62df3 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -5,7 +5,7 @@ import "../css/app.scss";
 
 // Import icons for sprite-loader
 // navbar brand icon
-import "../node_modules/bootstrap-icons/icons/calendar3-week.svg"; // brand
+import "../node_modules/bootstrap-icons/icons/calendar2-week.svg"; // brand
 // menus etc
 import "../node_modules/bootstrap-icons/icons/person-circle.svg"; // accounts menu
 import "../node_modules/bootstrap-icons/icons/person-plus.svg"; // new user / register
@@ -34,9 +34,10 @@ import "../node_modules/bootstrap-icons/icons/arrow-repeat.svg"; // resend confi
 import "../node_modules/@mdi/svg/svg/head-question-outline.svg"; // forgot password
 import "../node_modules/bootstrap-icons/icons/people.svg"; // users management
 // calendar/event icons
-import "../node_modules/bootstrap-icons/icons/calendar3.svg";
-import "../node_modules/bootstrap-icons/icons/calendar3-event.svg";
-import "../node_modules/bootstrap-icons/icons/calendar3-range.svg";
+import "../node_modules/bootstrap-icons/icons/calendar2.svg";
+import "../node_modules/bootstrap-icons/icons/calendar2-plus.svg";
+import "../node_modules/bootstrap-icons/icons/calendar2-event.svg";
+import "../node_modules/bootstrap-icons/icons/calendar2-range.svg";
 import "../node_modules/bootstrap-icons/icons/clock-history.svg"; // shift template
 import "../node_modules/bootstrap-icons/icons/tag.svg";
 import "../node_modules/bootstrap-icons/icons/hourglass.svg";
@@ -47,6 +48,11 @@ import "../node_modules/bootstrap-icons/icons/plus-circle-dotted.svg";
 import "../node_modules/bootstrap-icons/icons/clipboard-plus.svg";
 import "../node_modules/bootstrap-icons/icons/star.svg";
 import "../node_modules/bootstrap-icons/icons/star-fill.svg";
+import "../node_modules/bootstrap-icons/icons/binoculars.svg";
+import "../node_modules/bootstrap-icons/icons/binoculars-fill.svg";
+import "../node_modules/bootstrap-icons/icons/eraser.svg";
+import "../node_modules/bootstrap-icons/icons/save.svg";
+import "../node_modules/bootstrap-icons/icons/asterisk.svg";
 
 // webpack automatically bundles all modules in your
 // entry points. Those entry points can be configured
@@ -72,11 +78,13 @@ import "./_form-validity";
 // Bootstrap-liveview helpers
 import { AlertRemover } from "./_alert-remover";
 import { BsModal } from "./_bs_modal";
+import { BsCollapse } from "./_bs_collapse";
 
 // LiveSocket setup
 let Hooks = {};
 Hooks.AlertRemover = AlertRemover;
 Hooks.BsModal = BsModal;
+Hooks.BsCollapse = BsCollapse;
 let csrfToken = document
   .querySelector("meta[name='csrf-token']")
   .getAttribute("content");
diff --git a/lib/shift73k/shifts/templates.ex b/lib/shift73k/shifts/templates.ex
index 0e0c251e..993f73a7 100644
--- a/lib/shift73k/shifts/templates.ex
+++ b/lib/shift73k/shifts/templates.ex
@@ -40,8 +40,10 @@ defmodule Shift73k.Shifts.Templates do
       ** (Ecto.NoResultsError)
 
   """
+  def get_shift_template!(nil), do: nil
   def get_shift_template!(id), do: Repo.get!(ShiftTemplate, id)
 
+  def get_shift_template(nil), do: nil
   def get_shift_template(id), do: Repo.get(ShiftTemplate, id)
 
   @doc """
diff --git a/lib/shift73k/shifts/templates/shift_template.ex b/lib/shift73k/shifts/templates/shift_template.ex
index 5896ce7d..55ad1a56 100644
--- a/lib/shift73k/shifts/templates/shift_template.ex
+++ b/lib/shift73k/shifts/templates/shift_template.ex
@@ -58,7 +58,7 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
           []
       end
     end)
-    |> validate_inclusion(:time_zone, Timex.timezones())
+    |> validate_inclusion(:time_zone, Timex.timezones(), message: "must be a valid IANA tz database time zone")
   end
 
   defp time_start_from_attrs(%{"time_start" => time_start}), do: time_start
diff --git a/lib/shift73k_web/live/live_helpers.ex b/lib/shift73k_web/live/live_helpers.ex
index 6667f05a..a0b9ba77 100644
--- a/lib/shift73k_web/live/live_helpers.ex
+++ b/lib/shift73k_web/live/live_helpers.ex
@@ -5,6 +5,7 @@ defmodule Shift73kWeb.LiveHelpers do
   alias Shift73k.Accounts
   alias Shift73k.Accounts.User
   alias Shift73kWeb.UserAuth
+  alias Shift73k.Shifts.Templates.ShiftTemplate
 
   @doc """
   Performs the {:noreply, socket} for a given socket.
@@ -66,4 +67,17 @@ defmodule Shift73kWeb.LiveHelpers do
       put_flash(acc, String.to_existing_atom(k), v)
     end)
   end
+
+  def format_shift_time(time), do: Timex.format!(time, "{h12}:{m}{am}")
+
+  def format_shift_length(shift_template) do
+    shift_template
+    |> ShiftTemplate.shift_length()
+    |> Timex.Duration.from_minutes()
+    |> Timex.format_duration()
+    |> String.replace("PT", "")
+    |> String.replace("H", "h ")
+    |> String.replace("M", "m")
+    |> String.trim()
+  end
 end
diff --git a/lib/shift73k_web/live/shift_assign_live/index.ex b/lib/shift73k_web/live/shift_assign_live/index.ex
index 9ab3fec3..01e7326c 100644
--- a/lib/shift73k_web/live/shift_assign_live/index.ex
+++ b/lib/shift73k_web/live/shift_assign_live/index.ex
@@ -3,17 +3,98 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
   use Timex
 
   alias Shift73k.EctoEnums.WeekdayEnum
+  alias Shift73k.Shifts.Templates
+  alias Shift73k.Shifts.Templates.ShiftTemplate
+
+  @custom_shift %ShiftTemplate{subject: "Custom shift", id: "custom-shift"}
+  @custom_shift_opt {@custom_shift.subject, @custom_shift.id}
 
   @impl true
   def mount(_params, session, socket) do
     socket
     |> assign_defaults(session)
-    |> assign_day_names()
-    |> assign_init_dates(Timex.today())
-    |> assign_week_rows()
+    |> assign(:custom_shift, @custom_shift)
+    |> assign(:show_template_btn_active, :false)
+    |> assign(:show_template_details, :false)
+    |> assign(:selected_days, [])
     |> live_okreply()
   end
 
+  @impl true
+  def handle_params(_params, _url, socket) do
+    socket
+    |> init_shift_templates()
+    |> init_shift_template()
+    |> show_details_if_custom()
+    |> assign_shift_length()
+    |> assign_shift_template_changeset()
+    |> init_today(Timex.today())
+    |> init_calendar()
+    |> init_known_shifts()
+    |> live_noreply()
+  end
+
+  defp get_shift_template("custom-shift"), do: @custom_shift
+  defp get_shift_template(template_id), do: Templates.get_shift_template(template_id)
+
+  defp assign_shift_length(%{assigns: %{shift_template: shift}} = socket) do
+    assign(socket, :shift_length, format_shift_length(shift))
+  end
+
+  defp init_known_shifts(%{assigns: %{current_user: user, week_rows: weeks}} = socket) do
+    days = weeks |> List.flatten()
+    first = weeks |> List.flatten() |> List.first()
+    socket
+  end
+
+  defp init_calendar(%{assigns: %{current_user: user}} = socket) do
+    days = day_names(user.week_start_at)
+    {first, last, rows} = week_rows(socket.assigns.cursor_date, user.week_start_at)
+    assign(socket, [day_names: days, week_rows: rows, day_first: first, day_last: last])
+  end
+
+  defp init_today(socket, today) do
+    assign(socket, [current_date: today, cursor_date: today])
+  end
+
+  defp assign_shift_template_changeset(%{assigns: %{shift_template: shift}} = socket) do
+    cs = Templates.change_shift_template(shift)
+    assign(socket, :shift_template_changeset, cs)
+  end
+
+  defp init_shift_template(socket) do
+    first_list_id = socket.assigns.shift_templates |> hd() |> elem(1)
+    fave_id = socket.assigns.current_user.fave_shift_template_id
+    assign_shift_template(socket, (fave_id || first_list_id))
+  end
+
+  defp assign_shift_template(socket, template_id) do
+    assign(socket, :shift_template, get_shift_template(template_id))
+  end
+
+  defp init_shift_templates(%{assigns: %{current_user: user}} = socket) do
+    shift_templates =
+      Templates.list_shift_templates_by_user_id(user.id)
+      |> Enum.map(fn t -> shift_template_option(t, user.fave_shift_template_id) end)
+      |> Enum.concat([@custom_shift_opt])
+    assign(socket, :shift_templates, shift_templates)
+  end
+
+  defp shift_template_option(template, fave_id) do
+    label =
+      template.subject <> " (" <>
+      format_shift_time(template.time_start) <> "—" <>
+      format_shift_time(template.time_end) <> ")"
+
+    label =
+      case fave_id == template.id do
+        true -> label <> " ★"
+        false -> label
+      end
+
+    {label, template.id}
+  end
+
   defp rotate_week(week_start_at) do
     {a, b} = Enum.split_while(WeekdayEnum.__enum_map__(), fn {k, _v} -> k != week_start_at end)
     b ++ a
@@ -37,46 +118,160 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
       |> Timex.end_of_month()
       |> Timex.end_of_week(week_start_at)
 
-    Interval.new(from: first, until: last, right_open: false)
-    |> Enum.map(& &1)
-    |> Enum.chunk_every(7)
+    week_rows =
+      Interval.new(from: first, until: last, right_open: false)
+      |> Enum.map(& NaiveDateTime.to_date(&1))
+      |> Enum.chunk_every(7)
+
+    {first, last, week_rows}
   end
 
-  defp assign_day_names(socket) do
-    day_names = day_names(socket.assigns.current_user.week_start_at)
-    assign(socket, :day_names, day_names)
+  def day_color(day, current_date, cursor_date, selected_days) do
+    cond do
+      Enum.member?(selected_days, Date.to_string(day)) ->
+        cond do
+          Timex.compare(day, current_date, :days) == 0 -> "bg-triangle-info text-white"
+          day.month != cursor_date.month -> "bg-triangle-light text-gray"
+          true -> "bg-triangle-white"
+        end
+
+      Timex.compare(day, current_date, :days) == 0 -> "bg-info text-white"
+
+      day.month != cursor_date.month -> "bg-light text-gray"
+
+      true -> ""
+    end
   end
 
-  defp assign_init_dates(socket, today) do
-    assign(socket, [current_date: today, cursor_date: today])
+  defp prep_template_params(params, current_user) do
+    params
+    |> Map.put("time_start", Time.from_iso8601!("T#{params["time_start"]}:00"))
+    |> Map.put("time_end", Time.from_iso8601!("T#{params["time_end"]}:00"))
+    |> Map.put("user_id", current_user.id)
   end
 
-  defp assign_week_rows(socket) do
-    cursor_date = socket.assigns.cursor_date
-    week_start_at = socket.assigns.current_user.week_start_at
-
-    assign(socket, :week_rows, week_rows(cursor_date, week_start_at))
+  defp show_details_if_custom(socket) do
+    if (socket.assigns.shift_template.id != @custom_shift.id) || socket.assigns.show_template_details do
+      socket
+    else
+      socket
+      |> assign(:show_template_btn_active, :true)
+      |> push_event("toggle-template-details", %{targetId: "#templateDetailsCol"})
+    end
   end
 
-  def handle_event("prev-month", _, socket) do
-    cursor_date = Timex.shift(socket.assigns.cursor_date, months: -1)
+  defp set_month(socket, new_cursor_date) do
+    {first, last, rows} = week_rows(new_cursor_date, socket.assigns.current_user.week_start_at)
 
     assigns = [
-      cursor_date: cursor_date,
-      week_rows: week_rows(cursor_date, socket.assigns.current_user.week_start_at)
+      cursor_date: new_cursor_date,
+      week_rows: rows,
+      day_first: first,
+      day_last: last
     ]
 
-    {:noreply, assign(socket, assigns)}
+    assign(socket, assigns)
   end
 
-  def handle_event("next-month", _, socket) do
-    cursor_date = Timex.shift(socket.assigns.cursor_date, months: 1)
+  @impl true
+  def handle_event("validate-shift-template", %{"shift_template" => params}, socket) do
+    params = prep_template_params(params, socket.assigns.current_user)
+    shift_template = socket.assigns.shift_template
 
-    assigns = [
-      cursor_date: cursor_date,
-      week_rows: week_rows(cursor_date, socket.assigns.current_user.week_start_at)
-    ]
+    cs =
+      shift_template
+      |> Templates.change_shift_template(params)
+      |> Map.put(:action, :validate)
 
-    {:noreply, assign(socket, assigns)}
+    socket
+    |> assign(:shift_template_changeset, cs)
+    |> assign(:shift_template, Map.merge(shift_template, cs.changes))
+    |> assign_shift_length()
+    |> live_noreply()
+  end
+
+  @impl true
+  def handle_event("change-selected-template", %{"template_select" => %{"template" => template_id}}, socket) do
+    socket
+    |> assign_shift_template(template_id)
+    |> show_details_if_custom()
+    |> assign_shift_length()
+    |> assign_shift_template_changeset()
+    |> live_noreply()
+  end
+
+  @impl true
+  def handle_event("month-nav", %{"month" => direction}, socket) do
+    new_cursor =
+      cond do
+        direction == "now" -> Timex.today()
+        true ->
+          months = m = direction == "prev" && -1 || 1
+          Timex.shift(socket.assigns.cursor_date, months: months)
+      end
+
+    {:noreply, set_month(socket, new_cursor)}
+  end
+
+  # @impl true
+  # def handle_event("prev-month", _, socket) do
+  #   cursor_date = Timex.shift(socket.assigns.cursor_date, months: -1)
+  #   {first, last, rows} = week_rows(cursor_date, socket.assigns.current_user.week_start_at)
+
+  #   assigns = [
+  #     cursor_date: cursor_date,
+  #     week_rows: week_rows(cursor_date, socket.assigns.current_user.week_start_at)
+  #   ]
+
+  #   {:noreply, assign(socket, assigns)}
+  # end
+
+  # @impl true
+  # def handle_event("next-month", _, socket) do
+  #   cursor_date = Timex.shift(socket.assigns.cursor_date, months: 1)
+  #   {first, last, rows} = week_rows(cursor_date, socket.assigns.current_user.week_start_at)
+
+  #   assigns = [
+  #     cursor_date: cursor_date,
+  #     week_rows: rows,
+  #     day_first: first,
+  #     day_last: last
+  #   ]
+
+  #   {:noreply, assign(socket, assigns)}
+  # end
+
+  @impl true
+  def handle_event("toggle-template-details", %{"target_id" => target_id}, socket) do
+    socket
+    |> assign(:show_template_btn_active, !socket.assigns.show_template_btn_active)
+    |> push_event("toggle-template-details", %{targetId: target_id})
+    |> live_noreply()
+  end
+
+  @impl true
+  def handle_event("collapse-shown", %{"target_id" => _target_id}, socket) do
+    {:noreply, assign(socket, :show_template_details, :true)}
+  end
+
+  @impl true
+  def handle_event("collapse-hidden", %{"target_id" => _target_id}, socket) do
+    {:noreply, assign(socket, :show_template_details, :false)}
+  end
+
+  @impl true
+  def handle_event("select-day", %{"day" => day}, socket) do
+    selected_days =
+      case day_index = Enum.find_index(socket.assigns.selected_days, fn d -> d == day end) do
+        nil -> [day | socket.assigns.selected_days]
+        _ -> List.delete_at(socket.assigns.selected_days, day_index)
+      end
+
+    {:noreply, assign(socket, :selected_days, selected_days)}
+  end
+
+  @impl true
+  def handle_event("clear-days", _params, socket) do
+    {:noreply, assign(socket, :selected_days, [])}
   end
 end
diff --git a/lib/shift73k_web/live/shift_assign_live/index.html.leex b/lib/shift73k_web/live/shift_assign_live/index.html.leex
index c47db54a..6fe3c5f8 100644
--- a/lib/shift73k_web/live/shift_assign_live/index.html.leex
+++ b/lib/shift73k_web/live/shift_assign_live/index.html.leex
@@ -1,31 +1,192 @@
 <h2 class="mb-3 mb-sm-0">
-  <%= icon_div @socket, "bi-calendar3", [class: "icon baseline"] %>
+  <%= icon_div @socket, "bi-calendar2-plus", [class: "icon baseline"] %>
   Assign Shift To Dates
 </h2>
 
+<div class="row justify-content-center mt-4">
+  <div class="col-12 col-lg-9 col-xl-8 col-xxl-7 d-flex justify-content-start align-items-end">
+
+      <%= form_for :template_select, "#", [phx_change: "change-selected-template"], fn sts -> %>
+        <%= label sts, :template, "Select shift template to assign to dates", class: "form-label" %>
+        <%= select sts, :template, @shift_templates,
+            value: @shift_template && @shift_template.id || (@shift_template_options |> hd() |> elem(1)),
+            class: "form-select"
+          %>
+      <% end %>
+
+      <button type="button" class="ms-2 btn btn-primary text-nowrap <%= if @show_template_btn_active, do: "active" %>" id="#templateDetailsBtn" phx-click="toggle-template-details" phx-value-target_id="#templateDetailsCol">
+        <%= icon_div @socket, (@show_template_btn_active && "bi-binoculars-fill" || "bi-binoculars"), [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+        Details
+      </button>
+
+  </div>
+
+  <div class="col-12 col-lg-9 col-xl-8 col-xxl-7 <%= @show_template_details && "collapse show" || "collapse" %>" id="#templateDetailsCol" phx-hook="BsCollapse">
+    <div class="card mt-4">
+      <div class="card-body">
+
+        <%= form_for @shift_template_changeset, "#", [phx_change: "validate-shift-template"], fn stf -> %>
+
+          <div class="row">
+
+            <div class="col-12 col-md-6">
+              <%= label stf, :subject, "Subject/Title", class: "form-label" %>
+              <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(stf, :subject) %>">
+                <%= icon_div @socket, "bi-tag", [class: "icon is-left text-muted fs-5"] %>
+                <%= text_input stf, :subject,
+                    value: input_value(stf, :subject),
+                    class: input_class(stf, :subject, "form-control"),
+                    phx_debounce: 250,
+                    disabled: @shift_template.id != @custom_shift.id,
+                    aria_describedby: error_ids(stf, :subject)
+                  %>
+                <%= error_tag stf, :subject %>
+              </div>
+            </div>
+
+            <div class="col-12 col-md-6 mb-3">
+              <div class="row gx-2 gx-sm-3">
+
+                <div class="col-6" phx-feedback-for="<%= input_id(stf, :time_start) %>">
+                  <%= label stf, :time_start, "Start", class: "form-label" %>
+                  <%= time_input stf, :time_start,
+                      precision: :minute,
+                      value: input_value(stf, :time_start),
+                      class: input_class(stf, :time_start, "form-control"),
+                      disabled: @shift_template.id != @custom_shift.id,
+                      aria_describedby: error_ids(stf, :time_start)
+                    %>
+                </div>
+
+                <div class="col-6" phx-feedback-for="<%= input_id(stf, :time_end) %>">
+                  <%= label stf, :time_end, "End", class: "form-label" %>
+                  <%= time_input stf, :time_end,
+                      precision: :minute,
+                      value: input_value(stf, :time_end),
+                      class: input_class(stf, :time_end, "form-control"),
+                      disabled: @shift_template.id != @custom_shift.id,
+                      aria_describedby: error_ids(stf, :time_end)
+                    %>
+                </div>
+
+              </div>
+
+              <div class="valid-feedback d-block text-primary">Shift length: <%= @shift_length %></div>
+
+              <div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(stf, :time_start) %>">
+                <%= error_tag stf, :time_start %>
+              </div>
+              <div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(stf, :time_end) %>">
+                <%= error_tag stf, :time_end %>
+              </div>
+            </div>
+
+            <div class="col-12 col-md-6">
+              <%= label stf, :location, class: "form-label" %>
+              <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(stf, :location) %>">
+                <%= icon_div @socket, "bi-geo", [class: "icon is-left text-muted fs-5"] %>
+                <%= text_input stf, :location,
+                    value: input_value(stf, :location),
+                    class: input_class(stf, :location, "form-control"),
+                    phx_debounce: 250,
+                    disabled: @shift_template.id != @custom_shift.id,
+                    aria_describedby: error_ids(stf, :location)
+                  %>
+                <%= error_tag stf, :location %>
+              </div>
+            </div>
+
+            <div class="col-12 col-md-6">
+              <%= label stf, :time_zone, class: "form-label" %>
+              <div class="inner-addon left-addon mb-3 mb-md-0" phx-feedback-for="<%= input_id(stf, :time_zone) %>">
+                <%= icon_div @socket, "bi-map", [class: "icon is-left text-muted fs-5"] %>
+                <%= text_input stf, :time_zone,
+                    value: input_value(stf, :time_zone),
+                    class: input_class(stf, :time_zone, "form-control"),
+                    disabled: @shift_template.id != @custom_shift.id,
+                    phx_debounce: 250,
+                    list: "tz_list"
+                  %>
+                  <datalist id="tz_list">
+                    <%= for tz_name <- Timex.timezones() do %>
+                      <option value="<%= tz_name %>"></option>
+                    <% end %>
+                    end
+                  </datalist>
+                <%= if @shift_template.id == @custom_shift.id do %>
+                  <div class="valid-feedback d-block text-primary">Type to search & select from list of known <%= link "IANA tz database", to: "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones", target: "_blank" %> time zones</div>
+                <% end %>
+                <%= error_tag stf, :time_zone %>
+              </div>
+            </div>
+
+            <div class="col-12">
+              <%= label stf, :description, class: "form-label" %>
+              <div phx-feedback-for="<%= input_id(stf, :description) %>">
+
+                <%= textarea stf, :description,
+                    value: input_value(stf, :description),
+                    class: input_class(stf, :description, "form-control"),
+                    disabled: @shift_template.id != @custom_shift.id,
+                    phx_debounce: 250,
+                    aria_describedby: error_ids(stf, :description)
+                  %>
+                <%= error_tag stf, :description %>
+              </div>
+            </div>
+
+          </div>
+
+          <button type="submit" class="d-hidden"></button>
+
+        <% end %>
+
+      </div>
+    </div>
+
+  </div>
+</div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 <%# month navigation %>
-<div class="d-flex justify-content-between align-items-baseline mt-4">
-  <h3 class="ms-4 text-muted mb-0">
-    <%= Timex.format!(@cursor_date, "%B %Y", :strftime) %>
+<div class="d-flex justify-content-between align-items-end mt-4">
+  <h3 class="text-muted mb-0">
+    <%= Timex.format!(@cursor_date, "{Mfull} {YYYY}") %>
   </h3>
-  <div class="me-4">
-    <a href="#" phx-click="prev-month" class="btn btn-primary">
-      <%= icon_div @socket, "bi-chevron-left", [class: "icon baseline"] %>
+  <div>
+    <button type="button" phx-click="month-nav" phx-value-month="now" class="btn btn-info text-white" <%= if Map.get(@cursor_date, :month) == Map.get(Timex.today(), :month), do: "disabled" %>>
+      <%= icon_div @socket, "bi-asterisk", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+      Today
+    </button>
+    <button type="button" phx-click="month-nav" phx-value-month="prev" class="btn btn-primary">
+      <%= icon_div @socket, "bi-chevron-left", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
       Prev
-    </a>
-    <a href="#" phx-click="next-month" class="btn btn-primary">
+    </button>
+    <button type="button" phx-click="month-nav" phx-value-month="next" class="btn btn-primary">
       Next
-      <%= icon_div @socket, "bi-chevron-right", [class: "icon baseline"] %>
-    </a>
+      <%= icon_div @socket, "bi-chevron-right", [class: "icon baseline", style: "margin-left:0.125rem;"] %>
+    </button>
   </div>
 </div>
 
 <%# calendar month table display %>
-<table class="table table-rounded shadow mt-3">
+<table class="table table-rounded table-calendar shadow mt-3">
   <thead>
     <tr>
       <%= for {day_name, _i} <- Enum.with_index(@day_names) do %>
-        <th width="14%">
+        <th>
           <%= day_name %>
         </th>
       <% end %>
@@ -35,23 +196,29 @@
     <%= for week <- @week_rows do %>
       <tr>
       <%= for day <- week do %>
-        <%= cond do %>
-
-          <% Timex.compare(day, @current_date, :days) == 0 -> %>
-            <td width="14%" style="height: 6rem;" class="bg-info text-white">
-
-          <% day.month != @cursor_date.month -> %>
-            <td width="14%" style="height: 6rem;" class="bg-light text-gray">
-
-          <% true -> %>
-            <td width="14%" style="height: 6rem;">
-
-        <% end %>
-
-          <%= Timex.format!(day, "%d", :strftime) %>
+        <%# day |> NaiveDateTime.to_date() |> IO.inspect() %>
+        <td class="<%= day_color(day, @current_date, @cursor_date, @selected_days) %>" phx-click="select-day" phx-value-day="<%= day %>">
+          <%= Timex.format!(day, "{0D}") %><%= if day.month != @cursor_date.month, do: "-#{Timex.format!(day, "{Mshort}")}" %>
         </td>
       <% end %>
       </tr>
     <% end %>
   </tbody>
 </table>
+
+
+<div class="row justify-content-end mt-4">
+  <div class="col-auto">
+
+  <button class="btn btn-warning" phx-click="clear-days">
+    <%= icon_div @socket, "bi-eraser", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+    Clear
+  </button>
+
+  <button class="btn btn-primary" phx-click="save-days">
+    <%= icon_div @socket, "bi-save", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
+    Save assigned shifts
+  </button>
+
+  </div>
+</div>
diff --git a/lib/shift73k_web/live/shift_template_live/delete_component.html.leex b/lib/shift73k_web/live/shift_template_live/delete_component.html.leex
index d43ce9d1..a14f6a74 100644
--- a/lib/shift73k_web/live/shift_template_live/delete_component.html.leex
+++ b/lib/shift73k_web/live/shift_template_live/delete_component.html.leex
@@ -1,9 +1,9 @@
 <div class="modal-body">
 
   Are you sure you want to delete "<%= @delete_shift_template.subject %>
-    (<%= @delete_shift_template.time_start |> Timex.format!("{h12}:{m}{am}") %>
+    (<%= format_shift_time(@delete_shift_template.time_start) %>
     &mdash;
-    <%= @delete_shift_template.time_end |> Timex.format!("{h12}:{m}{am}") %>)"?
+    <%= format_shift_time(@delete_shift_template.time_end) %>)"?
 
 </div>
 <div class="modal-footer">
diff --git a/lib/shift73k_web/live/shift_template_live/form_component.ex b/lib/shift73k_web/live/shift_template_live/form_component.ex
index 33761b37..f1790457 100644
--- a/lib/shift73k_web/live/shift_template_live/form_component.ex
+++ b/lib/shift73k_web/live/shift_template_live/form_component.ex
@@ -1,8 +1,6 @@
 defmodule Shift73kWeb.ShiftTemplateLive.FormComponent do
   use Shift73kWeb, :live_component
 
-  import Shift73kWeb.ShiftTemplateLive.Index, only: [format_shift_length: 1]
-
   alias Shift73k.Shifts.Templates
   alias Shift73k.Shifts.Templates.ShiftTemplate
 
diff --git a/lib/shift73k_web/live/shift_template_live/form_component.html.leex b/lib/shift73k_web/live/shift_template_live/form_component.html.leex
index 73f0d7cb..8ea39d8b 100644
--- a/lib/shift73k_web/live/shift_template_live/form_component.html.leex
+++ b/lib/shift73k_web/live/shift_template_live/form_component.html.leex
@@ -13,6 +13,7 @@
           value: input_value(f, :subject),
           class: input_class(f, :subject, "form-control"),
           autofocus: true,
+          phx_debounce: 250,
           aria_describedby: error_ids(f, :subject)
         %>
       <%= error_tag f, :subject %>
@@ -59,6 +60,7 @@
       <%= text_input f, :location,
           value: input_value(f, :location),
           class: input_class(f, :location, "form-control"),
+          phx_debounce: 250,
           aria_describedby: error_ids(f, :location)
         %>
       <%= error_tag f, :location %>
@@ -71,6 +73,7 @@
       <%= textarea f, :description,
           value: input_value(f, :description),
           class: input_class(f, :description, "form-control"),
+          phx_debounce: 250,
           aria_describedby: error_ids(f, :description)
         %>
       <%= error_tag f, :description %>
@@ -82,7 +85,8 @@
       <%= icon_div @socket, "bi-map", [class: "icon is-left text-muted fs-5"] %>
       <%= text_input f, :time_zone,
           value: input_value(f, :time_zone),
-          class: "form-control",
+          class: input_class(f, :time_zone, "form-control"),
+          phx_debounce: 250,
           list: "tz_list"
         %>
         <datalist id="tz_list">
@@ -91,7 +95,7 @@
           <% end %>
           end
         </datalist>
-      <div class="valid-feedback d-block text-primary">Type to search & select from list of known time zones (<%= link "formatted per IANA", to: "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones", target: "_blank" %>)</div>
+      <div class="valid-feedback d-block text-primary">Type to search & select from list of known <%= link "IANA tz database", to: "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones", target: "_blank" %> time zones</div>
       <%= error_tag f, :time_zone %>
     </div>
 
diff --git a/lib/shift73k_web/live/shift_template_live/index.ex b/lib/shift73k_web/live/shift_template_live/index.ex
index 22ece55f..af877f65 100644
--- a/lib/shift73k_web/live/shift_template_live/index.ex
+++ b/lib/shift73k_web/live/shift_template_live/index.ex
@@ -107,14 +107,4 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
     socket |> put_flash(flash_type, msg) |> live_noreply()
   end
 
-  def format_shift_length(shift_template) do
-    shift_template
-    |> ShiftTemplate.shift_length()
-    |> Timex.Duration.from_minutes()
-    |> Timex.format_duration()
-    |> String.replace("PT", "")
-    |> String.replace("H", "h ")
-    |> String.replace("M", "m")
-    |> String.trim()
-  end
 end
diff --git a/lib/shift73k_web/live/shift_template_live/index.html.leex b/lib/shift73k_web/live/shift_template_live/index.html.leex
index 56b03aaf..75e40f00 100644
--- a/lib/shift73k_web/live/shift_template_live/index.html.leex
+++ b/lib/shift73k_web/live/shift_template_live/index.html.leex
@@ -25,7 +25,7 @@
         My Shift Templates
       </h2>
       <%= live_patch to: Routes.shift_template_index_path(@socket, :new), class: "btn btn-primary" do %>
-        <%= icon_div @socket, "bi-plus-circle-dotted", [class: "icon baseline me-1"] %>
+        <%= icon_div @socket, "bi-plus-circle-dotted", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
         New Shift Template
       <% end %>
     </div>
@@ -38,13 +38,14 @@
         <div class="col-12 col-lg-6">
 
           <div class="card mt-4">
-            <h5 class="card-header d-flex justify-content-between align-items-start">
-              <div class="visually-hidden">Subject:</div>
-              <%= template.subject %>
+            <h5 class="card-header d-flex justify-content-between align-items-center">
+              <span class="visually-hidden">Subject:</span>
+              <%= icon_div @socket, "bi-tag", [class: "icon baseline me-1"] %>
+              <div class="w-100"><%= template.subject %></div>
               <%= if template.id == @current_user.fave_shift_template_id do %>
-                <%= icon_div @socket, "bi-star-fill", [class: "icon baseline text-primary align-self-start ms-2", phx_click: "unset-user-fave-shift-template"] %>
+                <%= icon_div @socket, "bi-star-fill", [class: "icon baseline text-primary align-self-start ms-2"], [role: "img", aria_hidden: false, aria_label: "Unset as favorite", phx_click: "unset-user-fave-shift-template", class: "cursor-pointer"] %>
               <% else %>
-                <%= icon_div @socket, "bi-star", [class: "icon baseline text-primary align-self-start ms-2", phx_click: "set-user-fave-shift-template", phx_value_id: template.id] %>
+                <%= icon_div @socket, "bi-star", [class: "icon baseline text-primary align-self-start ms-2"], [role: "img", aria_hidden: false, aria_label: "Set as favorite", phx_click: "set-user-fave-shift-template", phx_value_id: template.id, class: "cursor-pointer"] %>
               <% end %>
             </h5>
             <div class="card-body">
@@ -53,13 +54,13 @@
                 <tbody>
                   <tr>
                     <th scope="row" class="text-end">
-                      <%= icon_div @socket, "bi-hourglass", [class: "icon text-muted"] %>
+                      <%= icon_div @socket, "bi-hourglass", [class: "icon baseline text-muted"] %>
                       <span class="visually-hidden">Hours:</span>
                     </th>
                     <td>
-                      <%= template.time_start |> Timex.format!("{h12}:{m}{am}") %>
+                      <%= format_shift_time(template.time_start) %>
                       &mdash;
-                      <%= template.time_end |> Timex.format!("{h12}:{m}{am}") %>
+                      <%= format_shift_time(template.time_end) %>
                       <span class="text-muted">
                         <span class="visually-hidden">Shift length:</span>
                         (<%= format_shift_length(template) %>)
diff --git a/lib/shift73k_web/live/user/settings/week_start.html.leex b/lib/shift73k_web/live/user/settings/week_start.html.leex
index fe95ba87..1d5221f8 100644
--- a/lib/shift73k_web/live/user/settings/week_start.html.leex
+++ b/lib/shift73k_web/live/user/settings/week_start.html.leex
@@ -6,7 +6,7 @@
 
     <%= label cvf, :week_start_at, class: "form-label" %>
     <div class="inner-addon left-addon mb-3">
-      <%= icon_div @socket, "bi-calendar3-range", [class: "icon is-left text-muted fs-5"] %>
+      <%= icon_div @socket, "bi-calendar2-range", [class: "icon is-left text-muted fs-5"] %>
       <%= select cvf, :week_start_at, week_start_options(),
           value: @current_user.week_start_at,
           class: "form-select"
diff --git a/lib/shift73k_web/live/user_management/index.html.leex b/lib/shift73k_web/live/user_management/index.html.leex
index cb38f2d6..d4f539ac 100644
--- a/lib/shift73k_web/live/user_management/index.html.leex
+++ b/lib/shift73k_web/live/user_management/index.html.leex
@@ -1,5 +1,5 @@
 <%= if @live_action in [:new, :edit] do %>
-  <%= live_modal @socket, Bones73kWeb.UserManagement.FormComponent,
+  <%= live_modal @socket, Shift73kWeb.UserManagement.FormComponent,
       id: @user.id || :new,
       title: @page_title,
       action: @live_action,
@@ -8,7 +8,7 @@
 <% end %>
 
 <%= if @delete_user do %>
-  <%= live_modal @socket, Bones73kWeb.UserManagement.DeleteComponent,
+  <%= live_modal @socket, Shift73kWeb.UserManagement.DeleteComponent,
       id: @delete_user.id,
       title: "Delete User",
       delete_user: @delete_user,
diff --git a/lib/shift73k_web/templates/layout/_navbar.html.eex b/lib/shift73k_web/templates/layout/_navbar.html.eex
index 611fa08a..e4dff3bf 100644
--- a/lib/shift73k_web/templates/layout/_navbar.html.eex
+++ b/lib/shift73k_web/templates/layout/_navbar.html.eex
@@ -3,7 +3,7 @@
 
     <h1 class="fs-4 my-0 py-0 lh-base">
     <%= link to: Routes.page_path(@conn, :index), class: "navbar-brand fs-4" do %>
-      <%= icon_div @conn, "bi-calendar3-week", [class: "icon baseline me-1"] %>
+      <%= icon_div @conn, "bi-calendar2-week", [class: "icon baseline me-1"] %>
       <span class="fw-light">Shift73k</span>
     <% end %>
     </h1>