diff --git a/src/modules/packagechooser/CMakeLists.txt b/src/modules/packagechooser/CMakeLists.txt
index 0c739012a..525921602 100644
--- a/src/modules/packagechooser/CMakeLists.txt
+++ b/src/modules/packagechooser/CMakeLists.txt
@@ -6,6 +6,7 @@ calamares_add_plugin( packagechooser
     SOURCES
         PackageChooserPage.cpp
         PackageChooserViewStep.cpp
+        PackageModel.cpp
     UI
         page_package.ui
     LINK_PRIVATE_LIBRARIES
diff --git a/src/modules/packagechooser/PackageChooserPage.cpp b/src/modules/packagechooser/PackageChooserPage.cpp
index 5524bd103..215adeeef 100644
--- a/src/modules/packagechooser/PackageChooserPage.cpp
+++ b/src/modules/packagechooser/PackageChooserPage.cpp
@@ -30,9 +30,7 @@ PackageChooserPage::PackageChooserPage( QWidget* parent )
     , ui( new Ui::PackageChooserPage )
 {
     ui->setupUi( this );
-    CALAMARES_RETRANSLATE(
-        updateLabels();
-    )
+    CALAMARES_RETRANSLATE( updateLabels(); )
 }
 
 void
@@ -42,3 +40,17 @@ PackageChooserPage::updateLabels()
     ui->productScreenshot->hide();
     ui->productDescription->setText( tr( "Please pick a product from the list." ) );
 }
+
+void
+PackageChooserPage::setModel( QAbstractItemModel* model )
+{
+    ui->products->setModel( model );
+}
+
+void
+PackageChooserPage::currentChanged( const QModelIndex& current )
+{
+    updateLabels();
+    cDebug() << "Current updated to" << current.row();
+    cDebug() << ui->products->model()->data( current, Qt::DisplayRole );
+}
diff --git a/src/modules/packagechooser/PackageChooserPage.h b/src/modules/packagechooser/PackageChooserPage.h
index 02c46fc3e..db5540312 100644
--- a/src/modules/packagechooser/PackageChooserPage.h
+++ b/src/modules/packagechooser/PackageChooserPage.h
@@ -19,6 +19,7 @@
 #ifndef PACKAGECHOOSERPAGE_H
 #define PACKAGECHOOSERPAGE_H
 
+#include <QAbstractItemModel>
 #include <QWidget>
 
 namespace Ui
@@ -32,8 +33,11 @@ class PackageChooserPage : public QWidget
 public:
     explicit PackageChooserPage( QWidget* parent = nullptr );
 
+    void setModel( QAbstractItemModel* model );
+
 public slots:
     void updateLabels();
+    void currentChanged( const QModelIndex& current );
 
 private:
     Ui::PackageChooserPage* ui;
diff --git a/src/modules/packagechooser/PackageChooserViewStep.cpp b/src/modules/packagechooser/PackageChooserViewStep.cpp
index db45bea0b..3ff2ac636 100644
--- a/src/modules/packagechooser/PackageChooserViewStep.cpp
+++ b/src/modules/packagechooser/PackageChooserViewStep.cpp
@@ -19,6 +19,7 @@
 #include "PackageChooserViewStep.h"
 
 #include "PackageChooserPage.h"
+#include "PackageModel.h"
 
 #include "GlobalStorage.h"
 #include "JobQueue.h"
@@ -35,6 +36,7 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlug
 PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
     : Calamares::ViewStep( parent )
     , m_widget( nullptr )
+    , m_model( nullptr )
 {
     emit nextStatusChanged( false );
 }
@@ -46,6 +48,7 @@ PackageChooserViewStep::~PackageChooserViewStep()
     {
         m_widget->deleteLater();
     }
+    delete m_model;
 }
 
 
@@ -62,6 +65,10 @@ PackageChooserViewStep::widget()
     if ( !m_widget )
     {
         m_widget = new PackageChooserPage( nullptr );
+        if ( m_model )
+        {
+            hookupModel();
+        }
     }
     return m_widget;
 }
@@ -110,4 +117,32 @@ PackageChooserViewStep::jobs() const
 void
 PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
 {
+    // TODO: use the configurationMap
+
+    if ( !m_model )
+    {
+
+        m_model = new PackageListModel( nullptr );
+        m_model->addPackage( PackageItem { "kde", "kde", "Plasma", "Plasma Desktop" } );
+        m_model->addPackage(
+            PackageItem { "gnome", "gnome", "GNOME", "GNU Networked Object Modeling Environment Desktop" } );
+
+
+        if ( m_widget )
+        {
+            hookupModel();
+        }
+    }
+}
+
+void
+PackageChooserViewStep::hookupModel()
+{
+    if ( !m_model || !m_widget )
+    {
+        cError() << "Can't hook up model until widget and model both exist.";
+        return;
+    }
+
+    m_widget->setModel( m_model );
 }
diff --git a/src/modules/packagechooser/PackageChooserViewStep.h b/src/modules/packagechooser/PackageChooserViewStep.h
index a32d4caab..56fbf5d3a 100644
--- a/src/modules/packagechooser/PackageChooserViewStep.h
+++ b/src/modules/packagechooser/PackageChooserViewStep.h
@@ -19,14 +19,17 @@
 #ifndef PACKAGECHOOSERVIEWSTEP_H
 #define PACKAGECHOOSERVIEWSTEP_H
 
-#include <PluginDllMacro.h>
-#include <utils/PluginFactory.h>
-#include <viewpages/ViewStep.h>
+#include "PluginDllMacro.h"
+#include "utils/PluginFactory.h"
+#include "viewpages/ViewStep.h"
 
 #include <QObject>
 #include <QUrl>
 #include <QVariantMap>
 
+class PackageChooserPage;
+class PackageListModel;
+
 class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
 {
     Q_OBJECT
@@ -52,7 +55,10 @@ public:
     void setConfigurationMap( const QVariantMap& configurationMap ) override;
 
 private:
-    QWidget* m_widget;
+    void hookupModel();
+
+    PackageChooserPage* m_widget;
+    PackageListModel* m_model;
 };
 
 CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )
diff --git a/src/modules/packagechooser/PackageModel.cpp b/src/modules/packagechooser/PackageModel.cpp
new file mode 100644
index 000000000..1f6346666
--- /dev/null
+++ b/src/modules/packagechooser/PackageModel.cpp
@@ -0,0 +1,93 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "PackageModel.h"
+
+#include "utils/Logger.h"
+
+PackageItem
+PackageItem::fromAppStream( const QString& filename )
+{
+    // TODO: implement this
+    return PackageItem {};
+}
+
+PackageItem::PackageItem() {}
+
+PackageItem::PackageItem( const QString& a_id,
+                          const QString& a_package,
+                          const QString& a_name,
+                          const QString& a_description )
+    : id( a_id )
+    , package( a_package )
+    , name( a_name )
+    , description( a_description )
+{
+}
+
+
+PackageListModel::PackageListModel( QObject* parent )
+    : QAbstractListModel( parent )
+{
+}
+
+PackageListModel::PackageListModel( PackageList&& items, QObject* parent )
+    : QAbstractListModel( parent )
+    , m_packages( std::move( items ) )
+{
+}
+
+PackageListModel::~PackageListModel() {}
+
+void
+PackageListModel::addPackage( PackageItem&& p )
+{
+    int c = m_packages.count();
+    beginInsertRows( QModelIndex(), c, c );
+    m_packages.append( p );
+    endInsertRows();
+}
+
+int
+PackageListModel::rowCount( const QModelIndex& index ) const
+{
+    // For lists, valid indexes have zero children; only the root index has them
+    return index.isValid() ? 0 : m_packages.count();
+}
+
+QVariant
+PackageListModel::data( const QModelIndex& index, int role ) const
+{
+    cDebug() << "Data" << m_packages.count() << index.isValid() << ( index.isValid() ? index.row() : -1 );
+    if ( !index.isValid() )
+    {
+        return QVariant();
+    }
+    int row = index.row();
+    if ( row >= m_packages.count() || row < 0 )
+    {
+        return QVariant();
+    }
+
+    if ( role == Qt::DisplayRole )
+    {
+        return m_packages[ row ].name;
+    }
+
+    return QVariant();
+}
diff --git a/src/modules/packagechooser/PackageModel.h b/src/modules/packagechooser/PackageModel.h
new file mode 100644
index 000000000..c00e55c0a
--- /dev/null
+++ b/src/modules/packagechooser/PackageModel.h
@@ -0,0 +1,62 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PACKAGEMODEL_H
+#define PACKAGEMODEL_H
+
+#include <QAbstractListModel>
+#include <QObject>
+#include <QVector>
+
+struct PackageItem
+{
+    QString id;
+    // TODO: may need more than one
+    QString package;
+    // TODO: name and description are localized
+    QString name;
+    QString description;
+    // TODO: may be more than one
+    // QPixmap screenshot;
+
+    PackageItem();
+    PackageItem( const QString& id, const QString& package, const QString& name, const QString& description );
+
+    // TODO: implement this
+    PackageItem fromAppStream( const QString& filename );
+};
+
+using PackageList = QVector< PackageItem >;
+
+class PackageListModel : public QAbstractListModel
+{
+public:
+    PackageListModel( PackageList&& items, QObject* parent );
+    PackageListModel( QObject* parent );
+    virtual ~PackageListModel();
+
+    void addPackage( PackageItem&& p );
+
+    int rowCount( const QModelIndex& index ) const override;
+    QVariant data( const QModelIndex& index, int role ) const override;
+
+private:
+    PackageList m_packages;
+};
+
+#endif