Skip to main content

DHTMLX Kanban. Adaptive columns fit the screen width example

On narrow screens or when the number of columns changes dynamically, fixed-width columns can overflow or leave wasted space. Adaptive columns stretch to fill the available width, ensuring the board always uses the full viewport regardless of screen size.

Live example​

const { Kanban, Toolbar, defaultEditorShape } = kanban;

const cardShape = {
    label: true,
    description: true,
    progress: true,
    comments: true,
    votes: {
      show: true,
      clickable: true,
    },
    start_date: true,
    end_date: true,
    users: {
        show: true,
        values: users,
    },
    priority: {
        show: true,
        values: [
            { id: 1, color: "#FF5252", label: "High", value: 1 },
            { id: 2, color: "#FFC975", label: "Medium", value: 2 },
            { id: 3, color: "#65D3B3", label: "Low", value: 3 },
        ],
    },
    color: true,
    menu: true,
    cover: true,
    attached: false,
};

const board = new Kanban("#root", {
    columns,
    cards,
    rows,
    rowKey: "type",
    cardShape,
    editorShape: [
        ...defaultEditorShape,
        {
            type: "comments",
            key: "comments",
            label: "Comments",
            config: {
                placement: "editor",
            },
        },
    ],
    currentUser: 1,
});

new kanban.Toolbar("#toolbar", {
    api: board.api,
});
<!-- styles for adaptive layout -->

<style>
#root .wx-header {
        width: 100%;
    }
    #root .wx-column {
        /* min width */
        --wx-kanban-column-width: 150px;
        /* auto size */
        flex-grow: 1;
    }
    #root .wx-image img {
        object-fit: cover;
    }

	#root .wx-card {
		container-type: inline-size;
		container-name: card;
	}

    #root .wx-card .wx-field.wx-image {
            height: 50cqw;
    }
	@container card (width < 250px) {
		.wx-footer .wx-users {
			display: none;
		}
	}
	@container card (width < 200px) {
		.wx-content .wx-footer {
			display: none;
		}
        .wx-body .wx-field {
            margin-bottom: 0 !important;
        }
        .wx-field > .wx-layout {
            display: none;
        }
	}

	#root .wx-kanban {
		container-type: inline-size;
		container-name: kanban;
	}
	@container kanban (width < 630px) {
		.wx-kanban .wx-sidebar-open {
			width: 100cqw;
			min-width: 100cqw;
		}
    }
</style>

<script>
function buildDateFromMD(md){const params = new URLSearchParams(typeof location !== 'undefined' ? location.search : '');const fixed = params.get('fixedYear') || window.FIXED_YEAR;const year = fixed ? Number(fixed) : new Date().getFullYear();const [m,d] = md.split('/').map(Number);return new Date(year, m - 1, d);}

    const users = [
		{
			id: 1,
			label: "Steve Smith",
			avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-1.jpg",
		},
		{
			id: 2,
			label: "Aaron Long",
			avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-2.jpg",
		},
		{
			id: 3,
			label: "Angela Allen",
			avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-3.jpg",
		},
		{
			id: 4,
			label: "Angela Long",
			avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-4.jpg",
		},
		{
			id: 5,
			label: "John Doe",
		},
	];
    
    const editorShape = [
		{
			type: "multiselect",
			key: "users",
			label: "Users",
			options: users,
		},
	];
    
    const columns = [
		{
			label: "Backlog",
			id: "backlog",
		},
		{
			label: "In progress",
			id: "inprogress",
		},
		{
			label: "Testing",
			id: "testing",
		},
		{
			label: "Done",
			id: "done",
		},
	];
    
    const rows = [
		{
			label: "Feature",
			id: "feature",
		},
		{
			label: "Task",
			id: "task",
		},
	];
    
    const cards = [
		{
			label: "Integration with Angular/React",
			priority: 1,
			color: "#65D3B3",
			start_date: buildDateFromMD("01/07"),
			users: [3, 2],
			column: "backlog",
			type: "feature",
            votes: [1],
            comments:[
                {
					id: 1,
					userId: 1,
					cardId: 1,
					text: "I look forward to seeing you at the integration meeting.",
					date: new Date(),
				},
            ]
		},
		{
			label: "Archive the cards/boards ",
			priority: 3,
			color: "#58C3FE",
			users: [4],
			progress: 1,
			column: "backlog",
			type: "feature",
		},
		{
			label: "Searching and filtering",
			priority: 1,
			color: "#58C3FE",
			start_date: buildDateFromMD("01/09"),
			users: [3, 1],
			progress: 1,
			column: "backlog",
			type: "task",
		},
		{
			label: "Set the tasks priorities",
			color: "#FFC975",
			start_date: buildDateFromMD("12/21"),
			users: [4],
			progress: 75,
			column: "inprogress",
			type: "feature",
			attached: [
				{
					isCover: true,
					coverURL: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-1.jpg",
					previewURL: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-1.jpg",
					url: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-1.jpg",
					name: "img-1.jpg",
				},
			],
            votes: [2]
		},
		{
			label: "Custom icons",
			color: "#65D3B3",
			start_date: buildDateFromMD("01/07"),
			users: [3, 2],
			column: "inprogress",
			type: "task",
		},
		{
			label: "Integration with Gantt",
			color: "#FFC975",
			start_date: buildDateFromMD("12/21"),
			users: [4],
			progress: 75,
			column: "inprogress",
			type: "task",
		},
		{
			label: "Drag and drop",
			priority: 1,
			color: "#58C3FE",
			users: [3, 1],
			progress: 100,
			column: "testing",
			type: "feature",
		},
		{
			label: "Adding images",
			color: "#58C3FE",
			users: [4],
			column: "testing",
			type: "task",
			attached: [
				{
					isCover: true,
					coverURL: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-2.jpg",
					previewURL: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-2.jpg",
					url: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/img-2.jpg",
					name: "img-2.jpg",
				},
			],
		},
		{
			label: "Create cards and lists from the UI and from code",
			priority: 3,
			color: "#65D3B3",
			start_date: buildDateFromMD("01/07"),
			users: [3, 2],
			column: "done",
			type: "feature",
		},
		{
			label: "Draw swimlanes",
			color: "#FFC975",
			users: [2],
			column: "done",
			type: "feature",
		},
		{
			label: "Progress bar",
			priority: 1,
			color: "#FFC975",
			start_date: buildDateFromMD("12/9"),
			users: [1, 4, 3],
			progress: 100,
			column: "done",
			type: "task",
		},
	];
</script>

This demo achieves responsive columns entirely through CSS - no JavaScript config needed. The key technique is setting flex-grow: 1 on .wx-column elements with a --wx-kanban-column-width minimum. CSS container queries (@container) progressively hide card details (user avatars, footers) as columns shrink below certain thresholds. The editor also adapts: on narrow boards (< 630px), it expands to full container width.

Solution overview​

  1. Set .wx-column { flex-grow: 1; --wx-kanban-column-width: 150px } to make columns fill available space
  2. Set .wx-header { width: 100% } to stretch column headers
  3. Use @container card (width < 250px) to hide user avatars on narrow cards
  4. Use @container card (width < 200px) to hide the entire footer on very narrow cards
  5. Use @container kanban (width < 630px) to make the editor full-width on small screens

Key points​

  • Pure CSS solution: No Kanban API properties needed - all adaptiveness comes from CSS flex-grow and container queries
  • Container queries, not media queries: @container responds to the component's own width, not the viewport - essential for embedded boards
  • Progressive degradation: Card details hide in stages as space shrinks, keeping the label always visible
  • Cover image scaling: height: 50cqw on cover images makes them scale proportionally with column width

API reference​

  • cardShape: Configures which card fields are visible

Additional resources​