diff --git a/src/group.h b/src/group.h
index 8c78e5abf..3c8eb6901 100644
--- a/src/group.h
+++ b/src/group.h
@@ -71,6 +71,8 @@ struct Group : GroupPool::PoolItem<&_group_pool> {
 	bool replace_protection;                ///< If set to true, the global autoreplace have no effect on the group
 	GroupStatistics statistics;             ///< NOSAVE: Statistics and caches on the vehicles in the group.
 
+	bool folded;                            ///< NOSAVE: Is this group folded in the group view?
+
 	GroupID parent;                         ///< Parent group
 
 	Group(CompanyID owner = INVALID_COMPANY, GroupID parent = INVALID_GROUP);
diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp
index 0cf6200b7..355aa8220 100644
--- a/src/group_cmd.cpp
+++ b/src/group_cmd.cpp
@@ -274,6 +274,8 @@ Group::Group(Owner owner, GroupID parent)
 {
 	this->owner = owner;
 	this->parent = parent;
+
+	this->folded = false;
 }
 
 Group::~Group()
diff --git a/src/group_gui.cpp b/src/group_gui.cpp
index 39921991f..31bae0af7 100644
--- a/src/group_gui.cpp
+++ b/src/group_gui.cpp
@@ -100,6 +100,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
 private:
 	/* Columns in the group list */
 	enum ListColumns {
+		VGC_FOLD,          ///< Fold / Unfold button.
 		VGC_NAME,          ///< Group name.
 		VGC_PROTECT,       ///< Autoreplace protect icon.
 		VGC_AUTOREPLACE,   ///< Autoreplace active icon.
@@ -128,7 +129,19 @@ private:
 			if ((*g)->parent == parent) {
 				*this->groups.Append() = *g;
 				*this->indents.Append() = indent;
-				AddParents(source, (*g)->index, indent + 1);
+				if ((*g)->folded) {
+					/* Test if this group has children at all. If not, the folded flag should be cleared to avoid lingering unfold buttons in the list. */
+					bool has_children = false;
+					for (const Group **child = source->Begin(); child != source->End(); child++) {
+						if ((*child)->parent == (*g)->index) {
+							has_children = true;
+							break;
+						}
+					}
+					Group::Get((*g)->index)->folded = has_children;
+				} else {
+					AddParents(source, (*g)->index, indent + 1);
+				}
 			}
 		}
 	}
@@ -192,9 +205,12 @@ private:
 	 */
 	uint ComputeGroupInfoSize()
 	{
+		this->column_size[VGC_FOLD] = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
+		this->tiny_step_height = this->column_size[VGC_FOLD].height;
+
 		this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype));
 		this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width);
-		this->tiny_step_height = this->column_size[VGC_NAME].height;
+		this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NAME].height);
 
 		this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT);
 		this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_PROTECT].height);
@@ -218,6 +234,7 @@ private:
 		this->tiny_step_height += WD_MATRIX_TOP;
 
 		return WD_FRAMERECT_LEFT + 8 +
+			this->column_size[VGC_FOLD].width + 2 +
 			this->column_size[VGC_NAME].width + 8 +
 			this->column_size[VGC_PROTECT].width + 2 +
 			this->column_size[VGC_AUTOREPLACE].width + 2 +
@@ -234,8 +251,9 @@ private:
 	 * @param g_id Group to list.
 	 * @param indent Indentation level.
 	 * @param protection Whether autoreplace protection is set.
+	 * @param has_children Whether the group has children and should have a fold / unfold button.
 	 */
-	void DrawGroupInfo(int y, int left, int right, GroupID g_id, int indent = 0, bool protection = false) const
+	void DrawGroupInfo(int y, int left, int right, GroupID g_id, int indent = 0, bool protection = false, bool has_children = false) const
 	{
 		/* Highlight the group if a vehicle is dragged over it */
 		if (g_id == this->group_over) {
@@ -249,6 +267,12 @@ private:
 		const GroupStatistics &stats = GroupStatistics::Get(this->vli.company, g_id, this->vli.vtype);
 		bool rtl = _current_text_dir == TD_RTL;
 
+		/* draw fold / unfold button */
+		int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_FOLD].width + 1 : left + WD_FRAMERECT_LEFT + 8;
+		if (has_children) {
+			DrawSprite(Group::Get(g_id)->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, rtl ? x - indent * LEVEL_WIDTH : x + indent * LEVEL_WIDTH, y + (this->tiny_step_height - this->column_size[VGC_FOLD].height) / 2);
+		}
+
 		/* draw group name */
 		StringID str;
 		if (IsAllGroupID(g_id)) {
@@ -259,7 +283,7 @@ private:
 			SetDParam(0, g_id);
 			str = STR_GROUP_NAME;
 		}
-		int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8;
+		x = rtl ? x - 2 - this->column_size[VGC_NAME].width : x + 2 + this->column_size[VGC_FOLD].width;
 		DrawString(x + indent * LEVEL_WIDTH, x + this->column_size[VGC_NAME].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NAME].height) / 2, str, colour);
 
 		/* draw autoreplace protection */
@@ -582,7 +606,7 @@ public:
 
 					assert(g->owner == this->owner);
 
-					DrawGroupInfo(y1, r.left, r.right, g->index, this->indents[i], g->replace_protection);
+					DrawGroupInfo(y1, r.left, r.right, g->index, this->indents[i], g->replace_protection, g->folded || (i + 1 < (int) this->groups.Length() && indents[i + 1] > this->indents[i]));
 
 					y1 += this->tiny_step_height;
 				}
@@ -656,6 +680,33 @@ public:
 				uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP, 0, this->tiny_step_height);
 				if (id_g >= this->groups.Length()) return;
 
+				if (groups[id_g]->folded || (id_g + 1 < this->groups.Length() && this->indents[id_g + 1] > this->indents[id_g])) {
+					/* The group has children, check if the user clicked the fold / unfold button. */
+					NWidgetCore *group_display = this->GetWidget<NWidgetCore>(widget);
+					int x = _current_text_dir == TD_RTL ?
+							group_display->pos_x + group_display->current_x - WD_FRAMERECT_RIGHT - 8 - this->indents[id_g] * LEVEL_WIDTH - this->column_size[VGC_FOLD].width :
+							group_display->pos_x + WD_FRAMERECT_LEFT + 8 + this->indents[id_g] * LEVEL_WIDTH;
+					if (click_count > 1 || (pt.x >= x && pt.x < (int) (x + this->column_size[VGC_FOLD].width))) {
+
+						GroupID g = this->vli.index;
+						if (!(IsAllGroupID(g) || IsDefaultGroupID(g))) {
+							do {
+								g = Group::Get(g)->parent;
+								if (g == groups[id_g]->index) {
+									this->vli.index = g;
+									break;
+								}
+							} while (g != INVALID_GROUP);
+						}
+
+						Group::Get(groups[id_g]->index)->folded = !groups[id_g]->folded;
+						this->groups.ForceRebuild();
+
+						this->SetDirty();
+						break;
+					}
+				}
+
 				this->group_sel = this->vli.index = this->groups[id_g]->index;
 
 				SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
