The hosts took an eighth-minute lead when Olivier Giroud converted a penalty following a handball by former Chelsea team-mate Tammy Abraham.
Milan made it 2-0 with Junior Messias’ follow-up after Giroud hit the post.
England striker Abraham pulled one back, steering in Lorenzo Pellegrini’s shot, but substitute Rafael Leao broke clear to score Milan’s third goal.
Zlatan Ibrahimovic had a chance to add a fourth in injury time, but saw his penalty, awarded after Leao had been brought down by Gianluca Mancini, saved by ex-Wolves goalkeeper Rui Patricio.
Mancini was shown his second yellow card for the challenge as Roma ended the game with nine men – right wing-back Rick Karsdorp had been sent off for two bookable offences in the 74th minute.
Abraham, who has now scored 13 goals for Roma in all competitions since his £34m move from Chelsea in August, had a chance to make it 2-2 in the second half but his shot from 22 yards was superbly pushed wide by home goalkeeper Mike Maignan.
The victory leaves Milan second on 45 points from 20 matches, after Inter’s game at Bologna was cancelled amid farcical scenes earlier on Thursday.
Bologna were banned from playing by their local health authority, but with Serie A refusing to cancel matches, Inter were forced to turn up and train before the game, as normal.
Roma, seventh in Serie A, are 14 points behind Inter and this was their first loss in five matches in all competitions.
Both Brahim Diaz and Alessandro Florenzi also hit the crossbar for Milan, who are hoping to win the Italian title for the first time since 2010-11.