diff --git a/gplugin-gtk3/gplugin-gtk-view.h b/gplugin-gtk4/gplugin-gtk-view.h
copy from gplugin-gtk3/gplugin-gtk-view.h
copy to gplugin-gtk4/gplugin-gtk-view.h
--- a/gplugin-gtk3/gplugin-gtk-view.h
+++ b/gplugin-gtk4/gplugin-gtk-view.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2021 Elliott Sales de Andrade <quantum.analyst@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -28,6 +28,8 @@
 
 #include <gtk/gtk.h>
 
+#include <gplugin.h>
+
 G_BEGIN_DECLS
 
 #define GPLUGIN_GTK_TYPE_VIEW (gplugin_gtk_view_get_type())
@@ -36,10 +38,15 @@ G_DECLARE_FINAL_TYPE(
 	gplugin_gtk_view,
 	GPLUGIN_GTK,
 	VIEW,
-	GtkTreeView)
+	GtkBox)
 
 GtkWidget *gplugin_gtk_view_new(void);
 
+void gplugin_gtk_view_set_manager(
+	GPluginGtkView *view,
+	GPluginManager *manager);
+GPluginManager *gplugin_gtk_view_get_manager(GPluginGtkView *view);
+
 void gplugin_gtk_view_set_show_internal(
 	GPluginGtkView *view,
 	gboolean show_internal);
diff --git a/gplugin-gtk3/gplugin-gtk-view.c b/gplugin-gtk4/gplugin-gtk-view.c
copy from gplugin-gtk3/gplugin-gtk-view.c
copy to gplugin-gtk4/gplugin-gtk-view.c
--- a/gplugin-gtk3/gplugin-gtk-view.c
+++ b/gplugin-gtk4/gplugin-gtk-view.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2021 Elliott Sales de Andrade <quantum.analyst@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -17,13 +17,13 @@
 
 #include <gplugin.h>
 
-#include <gplugin-gtk-store.h>
+#include <gplugin-gtk-plugin-row.h>
 #include <gplugin-gtk-view.h>
 
 /**
  * GPluginGtkView:
  *
- * A [class@Gtk.TreeView] widget that displays all the plugins and some basic
+ * A [class@Gtk.ListBox] widget that displays all the plugins and some basic
  * information about them.
  */
 
@@ -31,8 +31,13 @@
  * Structs
  *****************************************************************************/
 struct _GPluginGtkView {
-	GtkTreeView parent;
+	GtkBox parent;
 
+	GtkWidget *list_box;
+	GtkWidget *search_bar;
+	GtkWidget *search_entry;
+
+	GPluginManager *manager;
 	gboolean show_internal;
 };
 
@@ -41,6 +46,7 @@ struct _GPluginGtkView {
  *****************************************************************************/
 enum {
 	PROP_ZERO,
+	PROP_MANAGER,
 	PROP_SHOW_INTERNAL,
 	N_PROPERTIES,
 };
@@ -49,71 +55,92 @@ static GParamSpec *properties[N_PROPERTI
 };
 
 /******************************************************************************
- * Callbacks
+ * Helpers
  *****************************************************************************/
 static void
-gplugin_gtk_view_plugin_toggled_cb(
-	G_GNUC_UNUSED GtkCellRendererToggle *rend,
-	gchar *path,
+gplugin_gtk_view_row_activated(G_GNUC_UNUSED GtkListBox *box,
+                               GtkListBoxRow *row, G_GNUC_UNUSED gpointer data)
+{
+	GPluginGtkPluginRow *plugin_row = GPLUGIN_GTK_PLUGIN_ROW(row);
+
+	gplugin_gtk_plugin_row_set_expanded(
+		plugin_row,
+		!gplugin_gtk_plugin_row_get_expanded(plugin_row));
+}
+
+static void
+gplugin_gtk_view_add_plugins_to_list(
+	G_GNUC_UNUSED const gchar *id,
+	GSList *plugins,
 	gpointer data)
 {
 	GPluginGtkView *view = GPLUGIN_GTK_VIEW(data);
-	GPluginManager *manager = NULL;
-	GPluginPlugin *plugin = NULL;
-	GPluginPluginState state;
-	GtkTreeModel *model = NULL;
-	GtkTreeIter iter;
-	GtkTreePath *tree_path = NULL;
 
-	manager = gplugin_manager_get_default();
-
-	tree_path = gtk_tree_path_new_from_string(path);
+	for(; plugins; plugins = plugins->next) {
+		GtkWidget *row = gplugin_gtk_plugin_row_new();
+		gplugin_gtk_plugin_row_set_plugin(
+			GPLUGIN_GTK_PLUGIN_ROW(row),
+			GPLUGIN_PLUGIN(plugins->data));
+		gtk_list_box_append(GTK_LIST_BOX(view->list_box), row);
+	}
+}
 
-	model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
-	gtk_tree_model_get_iter(model, &iter, tree_path);
-	gtk_tree_path_free(tree_path);
+static int
+gplugin_gtk_view_sort_func(
+	GtkListBoxRow *row1,
+	GtkListBoxRow *row2,
+	G_GNUC_UNUSED gpointer data)
+{
+	GPluginGtkPluginRow *plugin_row1 = NULL, *plugin_row2 = NULL;
+	gchar *key1 = NULL, *key2 = NULL;
+	gint ret = 0;
 
-	gtk_tree_model_get(
-		model,
-		&iter,
-		GPLUGIN_GTK_STORE_PLUGIN_COLUMN,
-		&plugin,
-		-1);
+	plugin_row1 = GPLUGIN_GTK_PLUGIN_ROW(row1);
+	key1 = gplugin_gtk_plugin_row_get_sort_key(plugin_row1);
 
-	if(!GPLUGIN_IS_PLUGIN(plugin)) {
-		return;
-	}
+	plugin_row2 = GPLUGIN_GTK_PLUGIN_ROW(row2);
+	key2 = gplugin_gtk_plugin_row_get_sort_key(plugin_row2);
+
+	ret = g_strcmp0(key1, key2);
 
-	state = gplugin_plugin_get_state(plugin);
-	if(state == GPLUGIN_PLUGIN_STATE_LOADED) {
-		GError *error = NULL;
+	g_free(key1);
+	g_free(key2);
 
-		gplugin_manager_unload_plugin(manager, plugin, &error);
+	return ret;
+}
 
-		if(error != NULL) {
-			g_warning("Failed to unload plugin: %s", error->message);
+static gboolean
+gplugin_gtk_view_filter_func(GtkListBoxRow *row, gpointer data)
+{
+	GPluginGtkView *view = GPLUGIN_GTK_VIEW(data);
+	gboolean visible = TRUE;
 
-			g_error_free(error);
-		}
-	} else {
-		GError *error = NULL;
-
-		gplugin_manager_load_plugin(manager, plugin, &error);
-
-		if(error != NULL) {
-			g_warning("Failed to load plugin: %s", error->message);
-
-			g_error_free(error);
+	if(gtk_search_bar_get_search_mode(GTK_SEARCH_BAR(view->search_bar))) {
+		const gchar *text =
+			gtk_editable_get_text(GTK_EDITABLE(view->search_entry));
+		if(text != NULL && text[0] != '\0') {
+			GPluginGtkPluginRow *plugin_row = GPLUGIN_GTK_PLUGIN_ROW(row);
+			visible = gplugin_gtk_plugin_row_matches_search(plugin_row, text);
 		}
 	}
 
-	g_object_unref(G_OBJECT(plugin));
+	return visible;
+}
+
+static void
+gplugin_gtk_view_search_changed(
+	G_GNUC_UNUSED GtkSearchEntry *entry,
+	gpointer data)
+{
+	GPluginGtkView *view = GPLUGIN_GTK_VIEW(data);
+
+	gtk_list_box_invalidate_filter(GTK_LIST_BOX(view->list_box));
 }
 
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
-G_DEFINE_TYPE(GPluginGtkView, gplugin_gtk_view, GTK_TYPE_TREE_VIEW)
+G_DEFINE_TYPE(GPluginGtkView, gplugin_gtk_view, GTK_TYPE_BOX)
 
 static void
 gplugin_gtk_view_set_property(
@@ -125,6 +152,9 @@ gplugin_gtk_view_set_property(
 	GPluginGtkView *view = GPLUGIN_GTK_VIEW(obj);
 
 	switch(prop_id) {
+		case PROP_MANAGER:
+			gplugin_gtk_view_set_manager(view, g_value_get_object(value));
+			break;
 		case PROP_SHOW_INTERNAL:
 			gplugin_gtk_view_set_show_internal(
 				view,
@@ -146,6 +176,9 @@ gplugin_gtk_view_get_property(
 	GPluginGtkView *view = GPLUGIN_GTK_VIEW(obj);
 
 	switch(prop_id) {
+		case PROP_MANAGER:
+			g_value_set_object(value, gplugin_gtk_view_get_manager(view));
+			break;
 		case PROP_SHOW_INTERNAL:
 			g_value_set_boolean(
 				value,
@@ -158,64 +191,64 @@ gplugin_gtk_view_get_property(
 }
 
 static void
-gplugin_gtk_view_init(GPluginGtkView *view)
+gplugin_gtk_view_finalize(GObject *obj)
 {
-	GtkTreeViewColumn *col = NULL;
-	GtkCellRenderer *rend = NULL;
-
-	/* create the first column */
-	col = gtk_tree_view_column_new();
-	gtk_tree_view_column_set_title(col, "Enabled");
-	gtk_tree_view_column_set_resizable(col, FALSE);
-
-	rend = gtk_cell_renderer_toggle_new();
-	gtk_tree_view_column_pack_start(col, rend, FALSE);
-	g_signal_connect(
-		G_OBJECT(rend),
-		"toggled",
-		G_CALLBACK(gplugin_gtk_view_plugin_toggled_cb),
-		view);
+	GPluginGtkView *view = GPLUGIN_GTK_VIEW(obj);
 
-	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
-	gtk_tree_view_column_set_attributes(
-		col,
-		rend,
-		"active",
-		GPLUGIN_GTK_STORE_LOADED_COLUMN,
-		"sensitive",
-		GPLUGIN_GTK_STORE_ENABLED_COLUMN,
-		NULL);
+	g_clear_object(&view->manager);
 
-	/* create the markup column */
-	col = gtk_tree_view_column_new();
-	gtk_tree_view_column_set_title(col, "Name");
-	gtk_tree_view_column_set_resizable(col, FALSE);
-
-	rend = gtk_cell_renderer_text_new();
-	gtk_tree_view_column_pack_start(col, rend, TRUE);
-
-	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
-	gtk_tree_view_column_set_attributes(
-		col,
-		rend,
-		"markup",
-		GPLUGIN_GTK_STORE_MARKUP_COLUMN,
-		"sensitive",
-		GPLUGIN_GTK_STORE_ENABLED_COLUMN,
-		NULL);
+	G_OBJECT_CLASS(gplugin_gtk_view_parent_class)->finalize(obj);
 }
 
 static void
 gplugin_gtk_view_class_init(GPluginGtkViewClass *klass)
 {
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
 
 	obj_class->get_property = gplugin_gtk_view_get_property;
 	obj_class->set_property = gplugin_gtk_view_set_property;
+	obj_class->finalize = gplugin_gtk_view_finalize;
+
+	gtk_widget_class_set_template_from_resource(
+		widget_class,
+		"/org/imfreedom/keep/gplugin/gplugin-gtk/view.ui");
+
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkView,
+		list_box);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkView,
+		search_bar);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkView,
+		search_entry);
+
+	gtk_widget_class_bind_template_callback(
+		widget_class,
+		gplugin_gtk_view_row_activated);
+	gtk_widget_class_bind_template_callback(
+		widget_class,
+		gplugin_gtk_view_search_changed);
 
 	/* properties */
 
 	/**
+	 * GPluginGtkView:manager:
+	 *
+	 * The plugin manager to display.
+	 */
+	properties[PROP_MANAGER] = g_param_spec_object(
+		"manager",
+		"manager",
+		"The plugin manager to display",
+		GPLUGIN_TYPE_MANAGER,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * GPluginGtkView:show-internal:
 	 *
 	 * Whether or not to show internal plugins.
@@ -230,6 +263,23 @@ gplugin_gtk_view_class_init(GPluginGtkVi
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 }
 
+static void
+gplugin_gtk_view_init(GPluginGtkView *self)
+{
+	gtk_widget_init_template(GTK_WIDGET(self));
+
+	gtk_list_box_set_filter_func(
+		GTK_LIST_BOX(self->list_box),
+		gplugin_gtk_view_filter_func,
+		self,
+		NULL);
+	gtk_list_box_set_sort_func(
+		GTK_LIST_BOX(self->list_box),
+		gplugin_gtk_view_sort_func,
+		NULL,
+		NULL);
+}
+
 /******************************************************************************
  * API
  *****************************************************************************/
@@ -237,7 +287,7 @@ gplugin_gtk_view_class_init(GPluginGtkVi
 /**
  * gplugin_gtk_view_new:
  *
- * Creates a new [class@GPluginGtk3.View].
+ * Creates a new [class@GPluginGtk4.View].
  *
  * Returns: (transfer full): The new view.
  */
@@ -245,12 +295,10 @@ GtkWidget *
 gplugin_gtk_view_new(void)
 {
 	GObject *ret = NULL;
-	GPluginGtkStore *store = gplugin_gtk_store_new();
 
 	/* clang-format off */
 	ret = g_object_new(
 		GPLUGIN_GTK_TYPE_VIEW,
-		"model", GTK_TREE_MODEL(store),
 		NULL);
 	/* clang-format on */
 
@@ -258,6 +306,52 @@ gplugin_gtk_view_new(void)
 }
 
 /**
+ * gplugin_gtk_view_set_manager:
+ * @view: The GTK view instance.
+ * @manager: The plugin manager to display.
+ *
+ * This function will set which plugin manager to display.
+ */
+void
+gplugin_gtk_view_set_manager(GPluginGtkView *view, GPluginManager *manager)
+{
+	g_return_if_fail(GPLUGIN_GTK_IS_VIEW(view));
+	g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
+
+	if(g_set_object(&view->manager, manager)) {
+		GtkListBox *box = GTK_LIST_BOX(view->list_box);
+		GtkWidget *child = NULL;
+
+		while((child = gtk_widget_get_first_child(view->list_box)) != NULL) {
+			gtk_list_box_remove(box, child);
+		}
+
+		gplugin_manager_foreach(
+			manager,
+			gplugin_gtk_view_add_plugins_to_list,
+			view);
+
+		g_object_notify(G_OBJECT(view), "manager");
+	}
+}
+
+/**
+ * gplugin_gtk_view_get_manager:
+ * @view: The GTK view instance.
+ *
+ * Returns the plugin manager that is being displayed.
+ *
+ * Returns: (transfer none): The plugin manager to display.
+ */
+GPluginManager *
+gplugin_gtk_view_get_manager(GPluginGtkView *view)
+{
+	g_return_val_if_fail(GPLUGIN_GTK_IS_VIEW(view), FALSE);
+
+	return view->manager;
+}
+
+/**
  * gplugin_gtk_view_set_show_internal:
  * @view: The GTK view instance.
  * @show_internal: Whether or not to show internal plugins.
# HG changeset patch
# Node ID fbdd774eabdd
# Parent  7cda118a845e
diff --git a/gplugin-gtk4-viewer/data/window.ui b/gplugin-gtk4-viewer/data/window.ui
--- a/gplugin-gtk4-viewer/data/window.ui
+++ b/gplugin-gtk4-viewer/data/window.ui
@@ -7,18 +7,7 @@
     <property name="default-width">600</property>
     <property name="default-height">400</property>
     <child>
-      <object class="GtkScrolledWindow">
-        <property name="hscrollbar_policy">never</property>
-        <child>
-          <object class="GtkListBox">
-            <child type="placeholder">
-              <object class="GtkLabel">
-                <property name="label" translatable="yes">No plugins found</property>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
+      <object class="GPluginGtkView" id="view"/>
     </child>
   </template>
 </interface>
diff --git a/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.h b/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.h
--- a/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.h
+++ b/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.h
@@ -20,6 +20,8 @@
 
 #include <gtk/gtk.h>
 
+#include <gplugin-gtk.h>
+
 G_BEGIN_DECLS
 
 #define GPLUGIN_GTK_VIEWER_TYPE_WINDOW (gplugin_gtk_viewer_window_get_type())
@@ -30,6 +32,9 @@ G_DECLARE_FINAL_TYPE(
 	WINDOW,
 	GtkApplicationWindow)
 
+GPluginGtkView *gplugin_gtk_viewer_window_get_view(
+	GPluginGtkViewerWindow *window);
+
 G_END_DECLS
 
 #endif /* GPLUGIN_GTK_VIEWER_WINDOW_H */
diff --git a/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.c b/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.c
--- a/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.c
+++ b/gplugin-gtk4-viewer/gplugin-gtk-viewer-window.c
@@ -15,10 +15,14 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <gplugin-gtk.h>
+
 #include "gplugin-gtk-viewer-window.h"
 
 struct _GPluginGtkViewerWindow {
 	GtkApplicationWindow parent;
+
+	GPluginGtkView *view;
 };
 
 G_DEFINE_TYPE(
@@ -39,6 +43,11 @@ gplugin_gtk_viewer_window_class_init(GPl
 		widget_class,
 		"/org/imfreedom/keep/gplugin/gplugin/viewer/window.ui");
 
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkViewerWindow,
+		view);
+
 	gtk_widget_class_add_binding_action(
 		widget_class,
 		GDK_KEY_w,
@@ -52,3 +61,12 @@ gplugin_gtk_viewer_window_init(GPluginGt
 {
 	gtk_widget_init_template(GTK_WIDGET(self));
 }
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GPluginGtkView *
+gplugin_gtk_viewer_window_get_view(GPluginGtkViewerWindow *window)
+{
+	return window->view;
+}
diff --git a/gplugin-gtk4-viewer/gplugin-gtk-viewer.c b/gplugin-gtk4-viewer/gplugin-gtk-viewer.c
--- a/gplugin-gtk4-viewer/gplugin-gtk-viewer.c
+++ b/gplugin-gtk4-viewer/gplugin-gtk-viewer.c
@@ -79,12 +79,19 @@ activate_cb(GApplication *app)
 
 	window = gtk_application_get_active_window(GTK_APPLICATION(app));
 	if(window == NULL) {
+		GPluginGtkView *view = NULL;
+
 		/* clang-format off */
 		window = g_object_new(
 			GPLUGIN_GTK_VIEWER_TYPE_WINDOW,
 			"application", app,
 			NULL);
 		/* clang-format on */
+
+		view = gplugin_gtk_viewer_window_get_view(
+			GPLUGIN_GTK_VIEWER_WINDOW(window));
+		gplugin_gtk_view_set_manager(view, gplugin_manager_get_default());
+		gplugin_gtk_view_set_show_internal(view, show_internal);
 	}
 
 	gtk_window_present(window);
diff --git a/gplugin-gtk4/data/gplugin-gtk.gresource.xml b/gplugin-gtk4/data/gplugin-gtk.gresource.xml
--- a/gplugin-gtk4/data/gplugin-gtk.gresource.xml
+++ b/gplugin-gtk4/data/gplugin-gtk.gresource.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
-  <gresource prefix="/org/imfreedom/keep/gplugin/gplugin-gtk4/">
+  <gresource prefix="/org/imfreedom/keep/gplugin/gplugin-gtk/">
+    <file compressed="true">plugin-row.ui</file>
+    <file compressed="true">view.ui</file>
   </gresource>
 </gresources>
diff --git a/gplugin-gtk4/data/plugin-row.ui b/gplugin-gtk4/data/plugin-row.ui
new file mode 100644
--- /dev/null
+++ b/gplugin-gtk4/data/plugin-row.ui
@@ -0,0 +1,462 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <object class="GtkImage" id="image1">
+    <property name="can-focus">0</property>
+    <property name="margin-start">6</property>
+    <property name="margin-end">6</property>
+    <property name="margin-top">6</property>
+    <property name="margin-bottom">6</property>
+    <property name="icon-name">preferences-system-symbolic</property>
+  </object>
+  <template class="GPluginGtkPluginRow" parent="GtkListBoxRow">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="can-focus">0</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkBox" id="header">
+            <property name="vexpand">1</property>
+            <property name="can-focus">0</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkSwitch" id="enable">
+                <property name="valign">center</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="title_box">
+                <property name="hexpand">1</property>
+                <property name="can-focus">0</property>
+                <property name="valign">center</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="title">
+                    <property name="accessible-role">row-header</property>
+                    <property name="hexpand">1</property>
+                    <property name="can-focus">0</property>
+                    <property name="label">Plugin Name</property>
+                    <property name="wrap">1</property>
+                    <property name="wrap-mode">word-char</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <style>
+                      <class name="heading"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="summary">
+                    <property name="can-focus">0</property>
+                    <property name="halign">start</property>
+                    <property name="label">The plugin short summary</property>
+                    <property name="wrap">1</property>
+                    <property name="wrap-mode">word-char</property>
+                    <property name="ellipsize">end</property>
+                    <property name="xalign">0</property>
+                    <style>
+                      <class name="subtitle"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="version">
+                <property name="can-focus">0</property>
+                <property name="halign">end</property>
+                <property name="label">1337.0.0</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="config">
+                <property name="receives-default">1</property>
+                <property name="valign">center</property>
+                <property name="child">image1</property>
+                <style>
+                  <class name="circular"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkSeparator">
+                <property name="orientation">vertical</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkExpander" id="expander">
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+            <style>
+              <class name="header"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="revealer">
+            <property name="can-focus">0</property>
+            <property name="reveal-child" bind-source="expander" bind-property="expanded" bind-flags="bidirectional|sync-create"/>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="can-focus">0</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkLabel" id="description">
+                    <property name="can-focus">0</property>
+                    <property name="margin-start">12</property>
+                    <property name="margin-end">12</property>
+                    <property name="margin-top">12</property>
+                    <property name="margin-bottom">12</property>
+                    <property name="label">A longer description of what this plugin does.</property>
+                    <property name="wrap">1</property>
+                    <property name="xalign">0</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkListBox">
+                    <property name="can-focus">0</property>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="authors_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Authors</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="authors_box">
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="can-focus">0</property>
+                                    <property name="label">An Author &lt;author@example.com&gt;</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">11</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="website_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Website</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="website">
+                                <property name="can-focus">0</property>
+                                <property name="label">&lt;a href=&quot;https://pidgin.im&quot;&gt;https://pidgin.im&lt;/a&gt;</property>
+                                <property name="use-markup">1</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="dependencies_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Dependencies</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="dependencies_box">
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="can-focus">0</property>
+                                    <property name="label">(none)</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="error_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Error</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="error">
+                                <property name="can-focus">0</property>
+                                <property name="label">(none)</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="id_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">ID</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="id">
+                                <property name="can-focus">0</property>
+                                <property name="label">gplugin/example-plugin</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="filename_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Filename</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="filename">
+                                <property name="can-focus">0</property>
+                                <property name="label">/the/full/path/to/plugin.so</property>
+                                <property name="ellipsize">start</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="abi_version_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">ABI Version</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="abi_version">
+                                <property name="can-focus">0</property>
+                                <property name="label">DEADC0DE</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="loader_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Loader</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="loader">
+                                <property name="can-focus">0</property>
+                                <property name="label">GPluginExampleLoader</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="internal_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Internal</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="internal">
+                                <property name="can-focus">0</property>
+                                <property name="label">Maybe</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <property name="selectable">0</property>
+                        <property name="child">
+                          <object class="GtkBox">
+                            <property name="can-focus">0</property>
+                            <property name="margin-start">12</property>
+                            <property name="margin-end">12</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel" id="load_on_query_label">
+                                <property name="can-focus">0</property>
+                                <property name="label" translatable="yes">Load on Query</property>
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="load_on_query">
+                                <property name="can-focus">0</property>
+                                <property name="label">Maybe</property>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                    <style>
+                      <class name="nested"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </property>
+          </object>
+        </child>
+      </object>
+    </property>
+    <style>
+      <class name="content"/>
+      <class name="expander"/>
+    </style>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="authors_label"/>
+      <widget name="website_label"/>
+      <widget name="dependencies_label"/>
+      <widget name="error_label"/>
+      <widget name="id_label"/>
+      <widget name="filename_label"/>
+      <widget name="abi_version_label"/>
+      <widget name="loader_label"/>
+      <widget name="internal_label"/>
+      <widget name="load_on_query_label"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/gplugin-gtk4/data/view.ui b/gplugin-gtk4/data/view.ui
new file mode 100644
--- /dev/null
+++ b/gplugin-gtk4/data/view.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="GPluginGtkView" parent="GtkBox">
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkSearchBar" id="search_bar">
+        <property name="key-capture-widget">list_box</property>
+        <property name="show-close-button">1</property>
+        <child>
+          <object class="GtkSearchEntry" id="search_entry">
+            <signal name="search-changed" handler="gplugin_gtk_view_search_changed"/>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="hscrollbar_policy">never</property>
+        <property name="vexpand">1</property>
+        <child>
+          <object class="GtkFrame">
+            <property name="margin-bottom">24</property>
+            <property name="margin-end">24</property>
+            <property name="margin-start">24</property>
+            <property name="margin-top">24</property>
+            <child>
+              <object class="GtkListBox" id="list_box">
+                <property name="selection-mode">none</property>
+                <property name="show-separators">1</property>
+                <signal name="row-activated" handler="gplugin_gtk_view_row_activated"/>
+                <style>
+                  <class name="rich-list"/>
+                </style>
+                <child type="placeholder">
+                  <object class="GtkLabel">
+                    <property name="label" translatable="yes">No plugins found</property>
+                    <property name="margin-bottom">24</property>
+                    <property name="margin-end">24</property>
+                    <property name="margin-start">24</property>
+                    <property name="margin-top">24</property>
+                    <style>
+                      <class name="large-title"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/gplugin-gtk4/gplugin-gtk-plugin-row.h b/gplugin-gtk4/gplugin-gtk-plugin-row.h
new file mode 100644
--- /dev/null
+++ b/gplugin-gtk4/gplugin-gtk-plugin-row.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 Elliott Sales de Andrade <quantum.analyst@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(GPLUGIN_GTK_GLOBAL_HEADER_INSIDE) && \
+	!defined(GPLUGIN_GTK_COMPILATION)
+#error "only <gplugin-gtk.h> may be included directly"
+#endif
+
+#ifndef GPLUGIN_GTK_PLUGIN_ROW_H
+#define GPLUGIN_GTK_PLUGIN_ROW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#include <gplugin.h>
+
+G_BEGIN_DECLS
+
+#define GPLUGIN_GTK_TYPE_PLUGIN_ROW (gplugin_gtk_plugin_row_get_type())
+G_DECLARE_FINAL_TYPE(
+	GPluginGtkPluginRow,
+	gplugin_gtk_plugin_row,
+	GPLUGIN_GTK,
+	PLUGIN_ROW,
+	GtkListBoxRow)
+
+GtkWidget *gplugin_gtk_plugin_row_new(void);
+
+void gplugin_gtk_plugin_row_set_plugin(
+	GPluginGtkPluginRow *row,
+	GPluginPlugin *plugin);
+GPluginPlugin *gplugin_gtk_plugin_row_get_plugin(GPluginGtkPluginRow *row);
+gboolean gplugin_gtk_plugin_row_get_expanded(GPluginGtkPluginRow *row);
+void gplugin_gtk_plugin_row_set_expanded(GPluginGtkPluginRow *row, gboolean expanded);
+gchar *gplugin_gtk_plugin_row_get_sort_key(GPluginGtkPluginRow *row);
+gboolean gplugin_gtk_plugin_row_matches_search(
+	GPluginGtkPluginRow *row,
+	const gchar *text);
+
+G_END_DECLS
+
+#endif /* GPLUGIN_GTK_PLUGIN_ROW_H */
diff --git a/gplugin-gtk4/gplugin-gtk-plugin-row.c b/gplugin-gtk4/gplugin-gtk-plugin-row.c
new file mode 100644
--- /dev/null
+++ b/gplugin-gtk4/gplugin-gtk-plugin-row.c
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2021 Elliott Sales de Andrade <quantum.analyst@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n-lib.h>
+
+#include <gplugin.h>
+
+#include <gplugin-gtk-plugin-row.h>
+
+/**
+ * GPluginGtkPluginRow:
+ *
+ * A widget that displays a [class@GPlugin.Plugin] in a user friendly way,
+ * intended to be placed in a [class@Gtk.ListBox].
+ */
+
+/******************************************************************************
+ * Structs
+ *****************************************************************************/
+struct _GPluginGtkPluginRow {
+	GtkListBoxRow parent;
+
+	GPluginPlugin *plugin;
+	gulong signal_id;
+
+	/* Header */
+	GtkWidget *enable;
+	GtkWidget *title;
+	GtkWidget *summary;
+	GtkWidget *version;
+	GtkWidget *config;
+	GtkWidget *revealer;
+
+	/* Details */
+	GtkWidget *description;
+	GtkWidget *authors_box;
+	GtkWidget *website;
+	GtkWidget *dependencies_box;
+	GtkWidget *error;
+	GtkWidget *id;
+	GtkWidget *filename;
+	GtkWidget *abi_version;
+	GtkWidget *loader;
+	GtkWidget *internal;
+	GtkWidget *load_on_query;
+};
+
+/******************************************************************************
+ * Enums
+ *****************************************************************************/
+enum {
+	PROP_ZERO,
+	PROP_PLUGIN,
+	PROP_EXPANDED,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {
+	NULL,
+};
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+_gplugin_gtk_plugin_row_refresh(GPluginGtkPluginRow *row)
+{
+	GtkWidget *widget = NULL;
+	GError *error = NULL;
+	gchar *name = NULL, *version = NULL, *website = NULL;
+	gchar *summary = NULL, *description = NULL, *id = NULL, *abi_version = NULL;
+	gchar *loader = NULL;
+	gchar **authors = NULL;
+	gchar **dependencies = NULL;
+	guint32 abi_version_uint;
+	gboolean loq = FALSE, internal = FALSE;
+	const gchar *filename = NULL;
+
+	/* Remove all the children from the authors box. */
+	while((widget = gtk_widget_get_first_child(row->authors_box)) != NULL) {
+		gtk_box_remove(GTK_BOX(row->authors_box), widget);
+	}
+
+	/* Remove all the children from the dependencies box. */
+	while((widget = gtk_widget_get_first_child(row->dependencies_box)) !=
+		  NULL) {
+		gtk_box_remove(GTK_BOX(row->dependencies_box), widget);
+	}
+
+	/* Now get the info if we can. */
+	if(GPLUGIN_IS_PLUGIN(row->plugin)) {
+		GPluginPluginInfo *plugin_info = gplugin_plugin_get_info(row->plugin);
+		GPluginLoader *plugin_loader = gplugin_plugin_get_loader(row->plugin);
+
+		filename = gplugin_plugin_get_filename(row->plugin);
+		error = gplugin_plugin_get_error(row->plugin);
+
+		if(GPLUGIN_IS_LOADER(plugin_loader)) {
+			loader = g_strdup(G_OBJECT_TYPE_NAME(plugin_loader));
+		}
+
+		/* clang-format off */
+		g_object_get(
+			G_OBJECT(plugin_info),
+			"abi_version", &abi_version_uint,
+			"authors", &authors,
+			"summary", &summary,
+			"description", &description,
+			"dependencies", &dependencies,
+			"id", &id,
+			"internal", &internal,
+			"load-on-query", &loq,
+			"name", &name,
+			"version", &version,
+			"website", &website,
+			NULL);
+		/* clang-format on */
+
+		/* Finagle the website. */
+		if(website) {
+			gchar *markup = g_markup_printf_escaped(
+				"<a href=\"%s\">%s</a>",
+				website,
+				website);
+			g_free(website);
+			website = markup;
+		}
+
+		/* Finagle the abi_version. */
+		abi_version = g_strdup_printf("%08x", abi_version_uint);
+
+		g_clear_object(&plugin_loader);
+		g_clear_object(&plugin_info);
+	}
+
+	/* Add a default name if unavailable. */
+	if(name == NULL) {
+		gchar *basename = g_path_get_basename(filename);
+		name = g_strdup_printf(_("Unnamed Plugin: %s"), basename);
+		g_free(basename);
+	}
+
+	gtk_label_set_text(GTK_LABEL(row->title), name);
+	gtk_label_set_text(GTK_LABEL(row->version), version);
+	gtk_label_set_markup(GTK_LABEL(row->website), website);
+	gtk_label_set_text(GTK_LABEL(row->summary), summary);
+	gtk_label_set_text(GTK_LABEL(row->description), description);
+	gtk_widget_set_visible(row->description, description != NULL);
+	gtk_label_set_text(GTK_LABEL(row->id), id);
+	gtk_label_set_text(
+		GTK_LABEL(row->error),
+		(error != NULL) ? error->message : "(none)");
+	gtk_label_set_text(GTK_LABEL(row->filename), filename);
+	gtk_label_set_text(GTK_LABEL(row->abi_version), abi_version);
+	gtk_label_set_text(
+		GTK_LABEL(row->loader),
+		(loader != NULL) ? loader : _("Unknown"));
+	gtk_label_set_text(GTK_LABEL(row->internal), internal ? _("Yes") : _("No"));
+	gtk_label_set_text(GTK_LABEL(row->load_on_query), loq ? _("Yes") : _("No"));
+	/* FIXME: Set this correctly when plugins get proper configuration. */
+	gtk_widget_set_sensitive(row->config, FALSE);
+
+	/* Set the authors. */
+	if(authors) {
+		gint i = 0;
+
+		for(i = 0; authors[i]; i++) {
+			widget = gtk_label_new(authors[i]);
+			gtk_widget_set_halign(widget, GTK_ALIGN_START);
+			gtk_widget_set_valign(widget, GTK_ALIGN_START);
+			gtk_box_append(GTK_BOX(row->authors_box), widget);
+		}
+	} else {
+		widget = gtk_label_new(_("(none)"));
+		gtk_box_append(GTK_BOX(row->authors_box), widget);
+	}
+
+	/* Set the dependencies. */
+	if(dependencies) {
+		gint i = 0;
+
+		for(i = 0; dependencies[i]; i++) {
+			widget = gtk_label_new(dependencies[i]);
+			gtk_widget_set_halign(widget, GTK_ALIGN_START);
+			gtk_widget_set_valign(widget, GTK_ALIGN_START);
+			gtk_box_append(GTK_BOX(row->dependencies_box), widget);
+		}
+	} else {
+		widget = gtk_label_new(_("(none)"));
+		gtk_box_append(GTK_BOX(row->dependencies_box), widget);
+	}
+
+	g_clear_error(&error);
+	g_free(loader);
+	g_free(abi_version);
+	g_strfreev(authors);
+	g_free(summary);
+	g_free(description);
+	g_strfreev(dependencies);
+	g_free(id);
+	g_free(name);
+	g_free(version);
+	g_free(website);
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+gplugin_gtk_plugin_row_state_cb(
+	G_GNUC_UNUSED GObject *obj,
+	G_GNUC_UNUSED GParamSpec *pspec,
+	gpointer data)
+{
+	_gplugin_gtk_plugin_row_refresh(GPLUGIN_GTK_PLUGIN_ROW(data));
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(
+	GPluginGtkPluginRow,
+	gplugin_gtk_plugin_row,
+	GTK_TYPE_LIST_BOX_ROW)
+
+static void
+gplugin_gtk_plugin_row_set_property(
+	GObject *obj,
+	guint prop_id,
+	const GValue *value,
+	GParamSpec *pspec)
+{
+	GPluginGtkPluginRow *row = GPLUGIN_GTK_PLUGIN_ROW(obj);
+
+	switch(prop_id) {
+		case PROP_PLUGIN:
+			gplugin_gtk_plugin_row_set_plugin(row, g_value_get_object(value));
+			break;
+		case PROP_EXPANDED:
+			gplugin_gtk_plugin_row_set_expanded(row, g_value_get_boolean(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+gplugin_gtk_plugin_row_get_property(
+	GObject *obj,
+	guint prop_id,
+	GValue *value,
+	GParamSpec *pspec)
+{
+	GPluginGtkPluginRow *row = GPLUGIN_GTK_PLUGIN_ROW(obj);
+
+	switch(prop_id) {
+		case PROP_PLUGIN:
+			g_value_set_object(value, gplugin_gtk_plugin_row_get_plugin(row));
+			break;
+		case PROP_EXPANDED:
+			g_value_set_boolean(value, gplugin_gtk_plugin_row_get_expanded(row));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+gplugin_gtk_plugin_row_finalize(GObject *obj)
+{
+	GPluginGtkPluginRow *row = GPLUGIN_GTK_PLUGIN_ROW(obj);
+
+	if(row->signal_id != 0 && GPLUGIN_IS_PLUGIN(row->plugin)) {
+		g_signal_handler_disconnect(G_OBJECT(row->plugin), row->signal_id);
+	}
+
+	g_clear_object(&row->plugin);
+
+	G_OBJECT_CLASS(gplugin_gtk_plugin_row_parent_class)->finalize(obj);
+}
+
+static void
+gplugin_gtk_plugin_row_init(GPluginGtkPluginRow *row)
+{
+	gtk_widget_init_template(GTK_WIDGET(row));
+
+	row->signal_id = 0;
+}
+
+static void
+gplugin_gtk_plugin_row_class_init(GPluginGtkPluginRowClass *klass)
+{
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = gplugin_gtk_plugin_row_get_property;
+	obj_class->set_property = gplugin_gtk_plugin_row_set_property;
+	obj_class->finalize = gplugin_gtk_plugin_row_finalize;
+
+	/* properties */
+	properties[PROP_PLUGIN] = g_param_spec_object(
+		"plugin",
+		"plugin",
+		"The GPluginPlugin whose info should be displayed",
+		G_TYPE_OBJECT,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+	properties[PROP_EXPANDED] = g_param_spec_boolean(
+		"expanded",
+		"expanded",
+		"Whether the row has been opened to show details",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+	/* template stuff */
+	gtk_widget_class_set_template_from_resource(
+		widget_class,
+		"/org/imfreedom/keep/gplugin/gplugin-gtk/plugin-row.ui");
+
+	/* Header */
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		enable);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		title);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		summary);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		version);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		config);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		revealer);
+
+	/* Details */
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		description);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		authors_box);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		website);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		dependencies_box);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		error);
+	gtk_widget_class_bind_template_child(widget_class, GPluginGtkPluginRow, id);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		filename);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		abi_version);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		loader);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		internal);
+	gtk_widget_class_bind_template_child(
+		widget_class,
+		GPluginGtkPluginRow,
+		load_on_query);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * gplugin_gtk_plugin_row_new:
+ *
+ * Create a new [class@GPluginGtk4.View] which can be used to display info
+ * about a [class@GPlugin.Plugin].
+ *
+ * Returns: (transfer full): The new widget.
+ */
+GtkWidget *
+gplugin_gtk_plugin_row_new(void)
+{
+	return GTK_WIDGET(g_object_new(GPLUGIN_GTK_TYPE_PLUGIN_ROW, NULL));
+}
+
+/**
+ * gplugin_gtk_plugin_row_set_plugin:
+ * @row: The plugin row instance.
+ * @plugin: The plugin instance.
+ *
+ * Sets the [class@GPlugin.Plugin] that should be displayed.
+ *
+ * A @plugin value of %NULL will clear the widget.
+ */
+void
+gplugin_gtk_plugin_row_set_plugin(
+	GPluginGtkPluginRow *row,
+	GPluginPlugin *plugin)
+{
+	g_return_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row));
+
+	if(row->signal_id != 0 && GPLUGIN_IS_PLUGIN(row->plugin)) {
+		g_signal_handler_disconnect(row->plugin, row->signal_id);
+		row->signal_id = 0;
+	}
+
+	if(g_set_object(&row->plugin, plugin)) {
+		_gplugin_gtk_plugin_row_refresh(row);
+
+		if(GPLUGIN_IS_PLUGIN(plugin)) {
+			/* Connect a signal to refresh when the plugin's state changes.  We
+			 * can't use g_signal_connect_object because the plugin object never
+			 * gets destroyed, as the manager and the loader both keep a
+			 * reference to it and the GPluginGtkPluginRow widget is reused for
+			 * all plugins so that all means that we just have to manage the
+			 * callback ourselves.
+			 */
+			row->signal_id = g_signal_connect(
+				G_OBJECT(plugin),
+				"notify::state",
+				G_CALLBACK(gplugin_gtk_plugin_row_state_cb),
+				row);
+		}
+	}
+}
+
+/**
+ * gplugin_gtk_plugin_row_get_plugin:
+ * @row: The plugin row instance.
+ *
+ * Returns the [class@GPlugin.Plugin] that's being displayed.
+ *
+ * Returns: (transfer none): The plugin that's being displayed.
+ */
+GPluginPlugin *
+gplugin_gtk_plugin_row_get_plugin(GPluginGtkPluginRow *row)
+{
+	g_return_val_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row), NULL);
+
+	return row->plugin;
+}
+
+/**
+ * gplugin_gtk_plugin_row_get_expanded:
+ * @row: The plugin row instance.
+ *
+ * Returns whether the plugin row is showing detailed information.
+ *
+ * Returns: %TRUE if the plugin row is expanded, %FALSE otherwise.
+ */
+gboolean
+gplugin_gtk_plugin_row_get_expanded(GPluginGtkPluginRow *row)
+{
+	g_return_val_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row), FALSE);
+
+	return gtk_revealer_get_reveal_child(GTK_REVEALER(row->revealer));
+}
+
+/**
+ * gplugin_gtk_plugin_row_set_expanded:
+ * @row: The plugin row instance.
+ * @expanded: Whether the plugin row is expanded.
+ *
+ * Sets whether the plugin row is showing detailed information.
+ */
+void
+gplugin_gtk_plugin_row_set_expanded(GPluginGtkPluginRow *row, gboolean expanded)
+{
+	g_return_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row));
+
+	gtk_revealer_set_reveal_child(GTK_REVEALER(row->revealer), expanded);
+}
+
+/**
+ * gplugin_gtk_plugin_row_get_sort_key:
+ * @row: The plugin row instance.
+ *
+ * Returns a key that can be used to sort this row.
+ *
+ * Returns: The sort key.
+ */
+gchar *
+gplugin_gtk_plugin_row_get_sort_key(GPluginGtkPluginRow *row)
+{
+	g_return_val_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row), NULL);
+
+	return g_strdup(gtk_label_get_text(GTK_LABEL(row->title)));
+}
+
+/**
+ * gplugin_gtk_plugin_row_matches_search:
+ * @row: The plugin row instance.
+ * @text: The text to search for.
+ *
+ * Matches this row instance against some text to be searched for.
+ *
+ * Returns: Whether the row matches the text or not.
+ */
+gboolean
+gplugin_gtk_plugin_row_matches_search(
+	GPluginGtkPluginRow *row,
+	const gchar *text)
+{
+	const gchar *value = NULL;
+
+	g_return_val_if_fail(GPLUGIN_GTK_IS_PLUGIN_ROW(row), FALSE);
+
+	value = gtk_label_get_text(GTK_LABEL(row->title));
+	if(g_strstr_len(value, -1, text)) {
+		return TRUE;
+	}
+
+	value = gtk_label_get_text(GTK_LABEL(row->summary));
+	if(g_strstr_len(value, -1, text)) {
+		return TRUE;
+	}
+
+	value = gtk_label_get_text(GTK_LABEL(row->description));
+	if(g_strstr_len(value, -1, text)) {
+		return TRUE;
+	}
+
+	value = gtk_label_get_text(GTK_LABEL(row->filename));
+	if(g_strstr_len(value, -1, text)) {
+		return TRUE;
+	}
+
+	return FALSE;
+}
diff --git a/gplugin-gtk4/meson.build b/gplugin-gtk4/meson.build
--- a/gplugin-gtk4/meson.build
+++ b/gplugin-gtk4/meson.build
@@ -9,9 +9,13 @@ if GTK4.found()
 GPLUGIN_GTK4_LIBRARY_VERSION = '0.1.0'
 
 GPLUGIN_GTK4_SOURCES = [
+	'gplugin-gtk-plugin-row.c',
+	'gplugin-gtk-view.c',
 ]
 
 GPLUGIN_GTK4_HEADERS = [
+	'gplugin-gtk-plugin-row.h',
+	'gplugin-gtk-view.h',
 ]
 
 GPLUGIN_GTK4_PRIVATE_SOURCES = [
diff --git a/po/POTFILES b/po/POTFILES
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -5,6 +5,10 @@ gplugin-gtk3/data/plugin-info.ui
 gplugin-gtk3/gplugin-gtk-plugin-info.c
 gplugin-gtk3/gplugin-gtk-store.c
 gplugin-gtk3/gplugin-gtk-view.c
+gplugin-gtk4/data/plugin-row.ui
+gplugin-gtk4/data/view.ui
+gplugin-gtk4/gplugin-gtk-plugin-row.c
+gplugin-gtk4/gplugin-gtk-view.c
 gplugin-gtk4-viewer/data/window.ui
 gplugin-gtk4-viewer/gplugin-gtk-viewer-window.c
 gplugin-gtk4-viewer/gplugin-gtk-viewer.c
