diff --git a/po/POTFILES.in b/po/POTFILES.in
index 30868fe69d20ea130b6ba814061738e592df3cfc..cc9a7fc8753efcde687b55512bd00a53d81eb90c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,5 +5,6 @@ src/menu.c
 src/panel.c
 src/phosh.c
 src/settings.c
+src/ui/lockscreen.ui
 src/ui/settings-menu.ui
 src/ui/top-panel.ui
diff --git a/po/phosh.pot b/po/phosh.pot
index 0f8110154daf65be9c3f591ffd7f3df4427f777c..f2234fe19f387adb830e26f76ff47f038fcad6d1 100644
--- a/po/phosh.pot
+++ b/po/phosh.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: phosh\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-04-01 18:31+0200\n"
+"POT-Creation-Date: 2018-04-02 11:39+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/src/lockscreen.c b/src/lockscreen.c
index e99b12c3bcb90c0e147835c7ab47a6dbbb83f25e..eaa495289673065ba552b495ebd9f4e308c4f7d6 100644
--- a/src/lockscreen.c
+++ b/src/lockscreen.c
@@ -5,9 +5,21 @@
  */
 
 #include "config.h"
-
 #include "lockscreen.h"
 
+#include <string.h>
+#include <glib/gi18n.h>
+#include <math.h>
+
+#define HANDY_USE_UNSTABLE_API
+#include <libhandy-0.0/handy.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-wall-clock.h>
+
+#define TEST_PIN "1234"
+#define LOCKSCREEN_IDLE_SECONDS 5
+
 enum {
   LOCKSCREEN_UNLOCK,
   N_SIGNALS
@@ -22,18 +34,138 @@ typedef struct _PhoshLockscreen
 
 
 typedef struct PhoshLockscreen {
-  gint _dummy;
+  GtkWidget *stack;
+
+  /* info page */
+  GtkWidget *ebox_info;
+  GtkWidget *grid_info;
+  GtkWidget *lbl_clock;
+  GtkGesture *ebox_pan_gesture;
+
+  /* unlock page */
+  GtkWidget *grid_unlock;
+  GtkWidget *dialer_keypad;
+  GtkWidget *lbl_keypad;
+  GtkWidget *lbl_unlock_status;
+  guint      idle_timer;
+  gint64     last_input;
+
+  GnomeWallClock *wall_clock;
 } PhoshLockscreenPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (PhoshLockscreen, phosh_lockscreen, GTK_TYPE_WINDOW)
 
 
-/* FIXME: Temporarily add a button until we interface with pam */
 static void
-btn_clicked (PhoshLockscreen *lockscreen,
-             GtkButton *btn)
+show_info_page (PhoshLockscreen *self)
+{
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+
+  hdy_dialer_clear_number (HDY_DIALER (priv->dialer_keypad));
+  gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->ebox_info);
+}
+
+
+static gboolean
+keypad_check_idle (PhoshLockscreen *self)
+{
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+  gint64 now = g_get_monotonic_time ();
+
+  g_assert (PHOSH_IS_LOCKSCREEN (self));
+  if (now - priv->last_input > LOCKSCREEN_IDLE_SECONDS * 1000 * 1000) {
+    show_info_page (self);
+    priv->idle_timer = 0;
+    return FALSE;
+  }
+  return TRUE;
+}
+
+
+static void
+show_unlock_page (PhoshLockscreen *self)
+{
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+
+  gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->grid_unlock);
+  hdy_dialer_set_show_action_buttons (HDY_DIALER (priv->dialer_keypad), FALSE);
+  if (!priv->idle_timer) {
+    priv->last_input = g_get_monotonic_time ();
+    priv->idle_timer = g_timeout_add_seconds (LOCKSCREEN_IDLE_SECONDS,
+                                              (GSourceFunc) keypad_check_idle,
+                                              self);
+  }
+}
+
+
+static void
+keypad_update_labels (PhoshLockscreen *self)
+{
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+  const gchar *number;
+  g_autofree gchar *stars = NULL;
+
+  number = hdy_dialer_get_number (HDY_DIALER (priv->dialer_keypad));
+  stars = g_strnfill (strlen(number), '*');
+  gtk_label_set_label (GTK_LABEL (priv->lbl_keypad), stars);
+  gtk_label_set_label (GTK_LABEL (priv->lbl_unlock_status), _("Enter PIN to unlock"));
+}
+
+
+static void
+keypad_number_notified_cb (PhoshLockscreen *self)
+{
+  PhoshLockscreenPrivate *priv;
+  const gchar *number;
+
+  g_assert (PHOSH_IS_LOCKSCREEN (self));
+
+  priv = phosh_lockscreen_get_instance_private (self);
+
+  number = hdy_dialer_get_number (HDY_DIALER (priv->dialer_keypad));
+  if (strlen (number) == strlen (TEST_PIN)) {
+    if (!g_strcmp0 (number, TEST_PIN)) {
+      /* FIXME: compare to real PIN */
+      g_signal_emit(self, signals[LOCKSCREEN_UNLOCK], 0);
+    } else {
+      gtk_label_set_label (GTK_LABEL (priv->lbl_unlock_status), _("Wrong PIN"));
+      hdy_dialer_clear_number (HDY_DIALER (priv->dialer_keypad));
+    }
+  }
+  priv->last_input = g_get_monotonic_time ();
+}
+
+
+static void
+keypad_symbol_clicked_cb (PhoshLockscreen *self, gchar symbol, HdyDialer *keypad)
+{
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+  g_assert (PHOSH_IS_LOCKSCREEN (self));
+  g_assert (HDY_IS_DIALER (keypad));
+
+  keypad_update_labels (self);
+  priv->last_input = g_get_monotonic_time ();
+}
+
+
+static void
+info_pan_cb (PhoshLockscreen *self, GtkPanDirection dir, gdouble offset, GtkGesturePan *pan)
+{
+  if (dir == GTK_PAN_DIRECTION_UP)
+    show_unlock_page (self);
+}
+
+
+static void
+wall_clock_notify_cb (PhoshLockscreen *self,
+                      GParamSpec *pspec,
+                      GnomeWallClock *wall_clock)
 {
-  g_signal_emit(lockscreen, signals[LOCKSCREEN_UNLOCK], 0);
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+  const gchar *str;
+
+  str = gnome_wall_clock_get_clock(wall_clock);
+  gtk_label_set_text (GTK_LABEL (priv->lbl_clock), str);
 }
 
 
@@ -41,9 +173,7 @@ static void
 phosh_lockscreen_constructed (GObject *object)
 {
   PhoshLockscreen *self = PHOSH_LOCKSCREEN (object);
-  GtkWidget *grid;
-  GtkWidget *button;
-  GtkWidget *label;
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
 
   GdkDisplay *display = gdk_display_get_default ();
   /* There's no primary monitor on nested wayland so just use the
@@ -66,15 +196,37 @@ phosh_lockscreen_constructed (GObject *object)
       gtk_widget_get_style_context (GTK_WIDGET (self)),
       "phosh-lockscreen");
 
-  grid = gtk_widget_new (GTK_TYPE_GRID, "halign", GTK_ALIGN_CENTER, "valign", GTK_ALIGN_CENTER, NULL);
-  gtk_container_add (GTK_CONTAINER (self), grid);
+  gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->ebox_info);
+
+  priv->ebox_pan_gesture = gtk_gesture_pan_new (GTK_WIDGET (priv->ebox_info),
+                                                GTK_ORIENTATION_VERTICAL);
+
+  g_signal_connect_swapped (priv->ebox_pan_gesture,
+                            "pan",
+                            G_CALLBACK (info_pan_cb),
+                            self);
+
+  priv->wall_clock = g_object_new (GNOME_TYPE_WALL_CLOCK, NULL);
+  g_signal_connect_swapped (priv->wall_clock,
+                            "notify::clock",
+                            G_CALLBACK (wall_clock_notify_cb),
+                            self);
+  wall_clock_notify_cb (self, NULL, priv->wall_clock);
+}
+
+static void
+phosh_lockscreen_dispose (GObject *object)
+{
+  PhoshLockscreen *self = PHOSH_LOCKSCREEN (object);
+  PhoshLockscreenPrivate *priv = phosh_lockscreen_get_instance_private (self);
+
+  g_clear_object (&priv->wall_clock);
+  if (priv->idle_timer) {
+    g_source_remove (priv->idle_timer);
+    priv->idle_timer = 0;
+  }
 
-  /* FIXME: just a button for now */
-  label = gtk_label_new("Click to unlock desktop");
-  button = gtk_button_new_from_icon_name ("face-surprise-symbolic", GTK_ICON_SIZE_DIALOG);
-  gtk_grid_attach (GTK_GRID (grid), label, 2, 1, 1, 1);
-  gtk_grid_attach (GTK_GRID (grid), button, 2, 3, 1, 1);
-  g_signal_connect_swapped (button, "clicked", G_CALLBACK (btn_clicked), self);
+  G_OBJECT_CLASS (phosh_lockscreen_parent_class)->dispose (object);
 }
 
 
@@ -82,18 +234,43 @@ static void
 phosh_lockscreen_class_init (PhoshLockscreenClass *klass)
 {
   GObjectClass *object_class = (GObjectClass *)klass;
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
   object_class->constructed = phosh_lockscreen_constructed;
+  object_class->dispose = phosh_lockscreen_dispose;
 
   signals[LOCKSCREEN_UNLOCK] = g_signal_new ("lockscreen-unlock",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
       NULL, G_TYPE_NONE, 0);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/sm/puri/phosh/ui/lockscreen.ui");
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, stack);
+
+  /* unlock page */
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, grid_unlock);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, dialer_keypad);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, lbl_keypad);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, lbl_unlock_status);
+  gtk_widget_class_bind_template_callback_full (widget_class,
+                                                "keypad_symbol_clicked_cb",
+                                                G_CALLBACK(keypad_symbol_clicked_cb));
+  gtk_widget_class_bind_template_callback_full (widget_class,
+                                                "keypad_number_notified_cb",
+                                                G_CALLBACK(keypad_number_notified_cb));
+
+
+  /* info page */
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, ebox_info);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, grid_info);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshLockscreen, lbl_clock);
 }
 
 
 static void
 phosh_lockscreen_init (PhoshLockscreen *self)
 {
+  gtk_widget_init_template (GTK_WIDGET (self));
 }
 
 
diff --git a/src/meson.build b/src/meson.build
index 4ab6cf6e2a2ebb0a42f739c21e312ae6293a1d52..4de256def3ef3b47668a05b6c9c105e3c032a125 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -51,6 +51,7 @@ phosh_deps = [
   dependency('gtk+-3.0'),
   dependency('gtk+-wayland-3.0'),
   dependency('wayland-client'),
+  dependency('libhandy-0.0'),
   cc.find_library('m', required: false),
   cc.find_library('rt', required: false),
 ]
diff --git a/src/phosh.c b/src/phosh.c
index ebc72238480dbf6d27f7387e7ae3e6fa9f948cea..75b4724975e80c4f74abd49b7e4a91d3a23d9416 100644
--- a/src/phosh.c
+++ b/src/phosh.c
@@ -32,7 +32,7 @@
 #include "settings.h"
 
 /* FIXME: use org.gnome.desktop.session.idle-delay */
-#define LOCKSCREEN_TIMEOUT 60 * 1000
+#define LOCKSCREEN_TIMEOUT 300 * 1000
 
 enum {
   PHOSH_SHELL_PROP_0,
@@ -837,7 +837,6 @@ int main(int argc, char *argv[])
   textdomain (GETTEXT_PACKAGE);
   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
-
   gtk_init (&argc, &argv);
 
   g_object_new (PHOSH_TYPE_SHELL, NULL);
diff --git a/src/phosh.gresources.xml b/src/phosh.gresources.xml
index d633fa2618ae9f2769073544c29eb94a759f2f75..ce08d73a465b2186ce01b9d7878150f64e4d1369 100644
--- a/src/phosh.gresources.xml
+++ b/src/phosh.gresources.xml
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/sm/puri/phosh">
-    <file preprocess="xml-stripblanks">ui/top-panel.ui</file>
+    <file preprocess="xml-stripblanks">ui/lockscreen.ui</file>
     <file preprocess="xml-stripblanks">ui/settings-menu.ui</file>
+    <file preprocess="xml-stripblanks">ui/top-panel.ui</file>
     <file compressed="true">style.css</file>
   </gresource>
 </gresources>
diff --git a/src/ui/lockscreen.ui b/src/ui/lockscreen.ui
new file mode 100644
index 0000000000000000000000000000000000000000..a8cf178a77772ca05db27b591f2211bb433a057f
--- /dev/null
+++ b/src/ui/lockscreen.ui
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <requires lib="libhandy" version="0.0"/>
+  <template class="PhoshLockscreen" parent="GtkWindow">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="transition_duration">500</property>
+        <property name="transition_type">slide-up</property>
+        <child>
+          <object class="GtkGrid" id="grid_unlock">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="hexpand">False</property>
+            <property name="vexpand">False</property>
+            <child>
+              <object class="GtkLabel" id="lbl_keypad">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">10</property>
+                <property name="margin_bottom">10</property>
+                <attributes>
+                  <attribute name="font-desc" value="Cantarell Bold 16"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="HdyDialer" id="dialer_keypad">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <property name="margin_top">12</property>
+                <property name="margin_bottom">12</property>
+                <signal name="symbol-clicked" handler="keypad_symbol_clicked_cb" swapped="yes"/>
+                <signal name="notify::number" handler="keypad_number_notified_cb" swapped="yes"/>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="lbl_unlock_status">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">10</property>
+                <property name="margin_bottom">10</property>
+                <property name="label" translatable="yes">Enter PIN to unlock</property>
+                <attributes>
+                  <attribute name="font-desc" value="Cantarell 16"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="name">unlock</property>
+            <property name="title">unlock</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEventBox" id="ebox_info">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="above_child">True</property>
+            <child>
+              <object class="GtkGrid" id="grid_info">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkLabel" id="lbl_clock">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="label">00:00</property>
+                    <property name="single_line_mode">True</property>
+                    <property name="track_visited_links">False</property>
+                    <attributes>
+                      <attribute name="font-desc" value="Cantarell 64"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Slide up to unlock</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">info</property>
+            <property name="title">info</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
+  </template>
+</interface>